Skip to content

Performance: Implement Regex Compilation Caching for Semantic Classifier #33

Closed

Description

@unclesp1d3r

Background

StringyMcStringFace's semantic classifier needs to identify and tag various string patterns extracted from binaries, including:

  • URLs and domains
  • IP addresses (IPv4/IPv6)
  • File paths (POSIX/Windows)
  • Registry keys
  • GUIDs
  • Email addresses
  • JWT tokens
  • Base64 sequences
  • Format strings (printf-style)
  • User agent strings

Each classification requires regex pattern matching, and the classifier will process thousands of strings per binary. Without caching, regex compilation overhead becomes a significant performance bottleneck.

Problem Statement

Currently, the classification module (src/classification/mod.rs) is minimal. When implemented, it will need to compile multiple complex regex patterns. Compiling regex patterns on-demand for each string classification would:

  • Add significant CPU overhead (regex compilation is expensive)
  • Create unnecessary memory allocations
  • Slow down the overall analysis pipeline
  • Make the tool less suitable for batch processing

Proposed Solution

Implement a regex caching strategy using lazy initialization:

1. Add Dependencies

Add regex and once_cell (or use std::sync::LazyLock in Rust 2024) to Cargo.toml:

[dependencies]
regex = "1.11"
once_cell = "1.20"  # or use std::sync::LazyLock

2. Implement Lazy-Initialized Regex Cache

Create a module with static, lazily-initialized regex patterns:

use once_cell::sync::Lazy;
use regex::Regex;

pub struct PatternCache {
    pub url: &'static Lazy<Regex>,
    pub domain: &'static Lazy<Regex>,
    pub ipv4: &'static Lazy<Regex>,
    pub ipv6: &'static Lazy<Regex>,
    pub filepath_posix: &'static Lazy<Regex>,
    pub filepath_windows: &'static Lazy<Regex>,
    pub registry_key: &'static Lazy<Regex>,
    pub guid: &'static Lazy<Regex>,
    pub email: &'static Lazy<Regex>,
    pub base64: &'static Lazy<Regex>,
    pub format_string: &'static Lazy<Regex>,
    pub user_agent: &'static Lazy<Regex>,
}

static URL_PATTERN: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"https?://[\w.-]+(?:/[\w./?%&=-]*)?").unwrap()
});

static GUID_PATTERN: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"(?i)[{]?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[}]?").unwrap()
});

// ... additional patterns

3. Use Cached Patterns in Classifier

The semantic classifier should reference these static patterns:

pub fn classify_string(text: &str) -> Vec<Tag> {
    let mut tags = Vec::new();
    
    if URL_PATTERN.is_match(text) {
        tags.push(Tag::Url);
    }
    if GUID_PATTERN.is_match(text) {
        tags.push(Tag::Guid);
    }
    // ... additional classifications
    
    tags
}

4. Consider RegexSet for Multiple Patterns

For optimal performance when checking multiple patterns against the same text, consider using RegexSet:

use regex::RegexSet;

static PATTERN_SET: Lazy<RegexSet> = Lazy::new(|| {
    RegexSet::new(&[
        r"https?://.*",  // URL
        r"[{]?[0-9a-f]{8}-.*",  // GUID
        // ... more patterns
    ]).unwrap()
});

Implementation Tasks

  • Add regex dependency to Cargo.toml
  • Add once_cell or use std::sync::LazyLock for lazy initialization
  • Create src/classification/patterns.rs with cached regex patterns
  • Implement pattern definitions for all semantic tags
  • Create classify_string() function using cached patterns
  • Add unit tests for each pattern
  • Create benchmark suite to measure performance improvement
  • Document pattern syntax and matching behavior

Success Criteria

  • All regex patterns compiled once on first use
  • Zero regex compilation overhead during string classification
  • Measurable performance improvement (target: >90% reduction in classification time)
  • Comprehensive test coverage for all patterns
  • Benchmarks demonstrating caching effectiveness

Performance Benchmarks

Create benchmarks in benches/regex_caching.rs to measure:

  1. Compilation overhead: Time to compile patterns with/without caching
  2. Classification throughput: Strings classified per second
  3. Memory usage: Compare cached vs. on-demand compilation
  4. Batch processing: Time to classify 10k, 100k, 1M strings

Expected improvement: 10-100x faster classification depending on pattern complexity and string volume.

References

Related

  • Part of v0.1 MVP milestone
  • Blocks efficient implementation of semantic classification (requirement 8.3)
  • Required for achieving acceptable performance on large binaries

Activity

  1. added this to the v0.1 milestone on Sep 30, 2025
  2. self-assigned this
    on Sep 30, 2025
  3. added theissue type on Nov 10, 2025
  4. unclesp1d3r commented on Nov 10, 2025

    @unclesp1d3r
    MemberAuthor

    @coderabbitai Enhance this issue body with additional context and a proposed solution. Improve the issue title and assign the proper labels and other metadata.

  5. changed the title [-]Regex Caching[/-] [+]Performance: Implement Regex Compilation Caching for Semantic Classifier[/+] on Nov 10, 2025
  6. traycerai commented on Nov 10, 2025

    @traycerai

    Plan

    Observations

    The codebase is well-structured with a clear separation of concerns. The src/classification/mod.rs module is currently empty (just a comment), providing a clean slate for implementation. The Tag enum is already defined in src/types.rs with all necessary variants. The project uses Rust 2024 edition, allowing us to use std::sync::LazyLock instead of external crates like once_cell. The documentation in docs/src/classification.md provides detailed pattern specifications and examples. The existing benchmark infrastructure uses criterion, and tests use insta for snapshot testing. The container parsing layer is fully implemented, so the classification module will integrate with existing FoundString structures.

    Approach

    Implement a regex compilation caching system for the semantic classifier using Rust 2024's std::sync::LazyLock for lazy initialization. The implementation will create a dedicated patterns module with static, lazily-initialized regex patterns for all semantic tags (URLs, domains, IPs, file paths, registry keys, GUIDs, emails, Base64, format strings, and user agents). The main classification function will use these cached patterns to tag strings efficiently. The solution includes comprehensive unit tests for each pattern and benchmarks to measure the performance improvement of caching vs. on-demand compilation.

    Reasoning

    I explored the project structure by listing directories recursively, then examined key files including the classification module stub, Cargo.toml dependencies, type definitions, requirements document, architecture documentation, classification design docs, existing tests, and benchmarks. This revealed that the classification module needs to be built from scratch with regex caching as a core performance feature.

    Proposed File Changes

    📄 Cargo.toml (MODIFY) 🔗

    Add the regex dependency to the [dependencies] section. Use version 1.11 or later to ensure compatibility with the latest regex features and performance improvements. This is the only new dependency required since the project uses Rust 2024 edition which includes std::sync::LazyLock in the standard library, eliminating the need for once_cell.
    Add a new benchmark configuration entry in the [[bench]] section (after the existing elf benchmark at line 38-40):

    [[bench]]
    name = "regex_caching"
    harness = false

    This registers the new benchmark with Cargo so it can be run with cargo bench --bench regex_caching.

    📄 src/classification/patterns.rs (NEW) 🔗

    References

    Create a new module containing all lazily-initialized regex patterns using std::sync::LazyLock. Define static patterns for each semantic tag:

    • URL_PATTERN: Match HTTP/HTTPS URLs with the pattern from docs/src/classification.md (line 17): https?://[^\s]+
    • DOMAIN_PATTERN: Match domain names with valid TLD structure (line 24): [a-zA-Z0-9.-]+\.[a-zA-Z]{2,}
    • IPV4_PATTERN: Match IPv4 addresses with word boundaries (line 31): \b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b
    • IPV6_PATTERN: Match IPv6 addresses (line 32): \b(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\b
    • FILEPATH_POSIX_PATTERN: Match POSIX file paths (line 41): /[^\0\n\r]*
    • FILEPATH_WINDOWS_PATTERN: Match Windows file paths (line 42): [A-Za-z]:\\[^\0\n\r]*
    • REGISTRY_KEY_PATTERN: Match Windows registry paths (line 49): HKEY_[A-Z_]+\\[^\0\n\r]*
    • GUID_PATTERN: Match GUIDs with optional braces, case-insensitive (line 57): (?i)[{]?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[}]?
    • EMAIL_PATTERN: Match email addresses (line 64): [a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}
    • BASE64_PATTERN: Match Base64 sequences with padding (line 80): [A-Za-z0-9+/]{20,}={0,2}
    • FORMAT_STRING_PATTERN: Match printf-style format strings (line 73): %[sdxo]|%\d+[sdxo]|\{\d+\}
    • USER_AGENT_PATTERN: Match common user agent strings (line 87): Mozilla/[0-9.]+|Chrome/[0-9.]+|Safari/[0-9.]+

    Each pattern should be wrapped in a LazyLock<Regex> with proper error handling using .unwrap() (acceptable for static patterns that are validated at compile time). Include comprehensive documentation comments explaining each pattern's purpose, examples of matches, and any edge cases.

    📄 src/classification/mod.rs (MODIFY) 🔗

    References

    Replace the stub comment with a complete classification implementation:

    1. Module declaration: Declare the patterns submodule and make it public for testing purposes

    2. Import statements: Import necessary types from crate::types including Tag, and import the patterns from the patterns module

    3. Main classification function: Implement pub fn classify_string(text: &str) -> Vec<Tag> that:

      • Takes a string slice as input
      • Returns a vector of Tag enums representing all matching semantic categories
      • Uses the cached regex patterns from patterns.rs to perform matching
      • Checks patterns in a logical order (e.g., check URL before Domain to avoid false positives)
      • Handles multiple tags per string (a string can be both a URL and contain Base64)
      • Uses is_match() for simple boolean checks
      • Applies the pattern matching logic described in docs/src/classification.md (lines 108-133)
    4. Pattern matching order: Implement the classification in this order to handle overlapping patterns correctly:

      • First check URL (most specific network indicator)
      • Then Domain (only if not already tagged as URL)
      • Check IPv4 and IPv6 patterns
      • Check file paths (Windows first, then POSIX to handle edge cases)
      • Check registry keys (Windows-specific)
      • Check GUID pattern
      • Check email addresses
      • Check Base64 sequences
      • Check format strings
      • Check user agent strings
    5. Documentation: Add comprehensive module-level documentation explaining the classification system, performance characteristics of regex caching, and usage examples

    6. Helper functions: Consider adding private helper functions for complex validation logic if needed (e.g., validating IP address ranges, checking TLD validity)

    The implementation should follow the architecture described in docs/src/architecture.md (lines 52-60) and align with the FoundString structure in src/types.rs (lines 143-163).
    Add a module declaration for the tests submodule at the end of the file:

    #[cfg(test)]
    mod tests;

    This follows Rust conventions for organizing unit tests in a separate file while keeping them in the same module hierarchy.

    📄 src/classification/tests.rs (NEW) 🔗

    References

    Create comprehensive unit tests for the classification system:

    1. Test module setup: Use #[cfg(test)] and import necessary types from the parent module and crate::types::Tag

    2. Pattern-specific tests: Create individual test functions for each semantic tag:

      • test_url_classification: Test various URL formats (http, https, with paths, with parameters)
      • test_domain_classification: Test domain names with different TLDs, ensure URLs are not double-tagged
      • test_ipv4_classification: Test valid IPv4 addresses, edge cases like 0.0.0.0, 255.255.255.255
      • test_ipv6_classification: Test IPv6 addresses in various formats
      • test_filepath_posix_classification: Test POSIX paths like /usr/bin/app, /etc/config
      • test_filepath_windows_classification: Test Windows paths like C:\\Windows\\System32\\file.dll
      • test_registry_key_classification: Test registry paths like HKEY_LOCAL_MACHINE\\SOFTWARE\\...
      • test_guid_classification: Test GUIDs with and without braces, different cases
      • test_email_classification: Test valid email addresses
      • test_base64_classification: Test Base64 sequences with proper padding
      • test_format_string_classification: Test printf-style format strings like Error: %s at line %d
      • test_user_agent_classification: Test user agent strings
    3. Edge case tests:

      • test_multiple_tags: Verify strings can receive multiple tags (e.g., a URL containing Base64)
      • test_no_match: Verify plain strings without patterns return empty tag vectors
      • test_empty_string: Verify empty strings are handled gracefully
      • test_special_characters: Test strings with special regex characters
    4. False positive tests: Test strings that should NOT match:

      • Invalid IP addresses (e.g., 999.999.999.999)
      • Malformed URLs
      • Short strings that might partially match patterns
    5. Performance tests: Add tests that verify patterns are compiled only once by calling classification multiple times and ensuring consistent behavior

    Follow the testing patterns established in tests/integration_elf.rs for consistency with the project's testing style.

    📄 benches/regex_caching.rs (NEW) 🔗

    References

    Create a comprehensive benchmark suite using criterion to measure the performance improvement of regex caching:

    1. Benchmark setup: Import criterion macros (criterion_group!, criterion_main!), the classification module, and std::hint::black_box for preventing compiler optimizations

    2. Test data preparation: Create representative test strings for benchmarking:

      • URLs: https://api.example.com/v1/users?id=123
      • Domains: malware-c2.example.com
      • IPv4: 192.168.1.100
      • File paths: /usr/local/bin/application, C:\\Windows\\System32\\kernel32.dll
      • Registry keys: HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run
      • GUIDs: {12345678-1234-5678-1234-567812345678}
      • Emails: admin@example.com
      • Format strings: Error: %s at line %d in file %s
      • Base64: SGVsbG8gV29ybGQhIFRoaXMgaXMgYSB0ZXN0IHN0cmluZyBmb3IgYmVuY2htYXJraW5n
      • User agents: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
      • Mixed strings with multiple patterns
      • Plain strings with no patterns
    3. Benchmark functions:

      • bench_single_classification: Benchmark classifying a single string (measures overhead of pattern access)
      • bench_batch_classification_small: Classify 100 strings (typical use case)
      • bench_batch_classification_medium: Classify 1,000 strings
      • bench_batch_classification_large: Classify 10,000 strings (stress test)
      • bench_mixed_patterns: Benchmark with strings that match multiple patterns
      • bench_no_match: Benchmark with strings that don't match any patterns (worst case)
    4. Throughput measurement: Add benchmarks that report strings classified per second to make performance improvements more tangible

    5. Memory efficiency note: Add comments explaining that LazyLock ensures patterns are compiled once and reused, with zero runtime compilation overhead after first use

    6. Criterion configuration: Use criterion_group! and criterion_main! macros following the pattern in benches/elf.rs (lines 66-72)

    The benchmarks should demonstrate the >90% reduction in classification time mentioned in the GitHub issue by showing that cached patterns eliminate regex compilation overhead.

    📄 src/lib.rs (MODIFY) 🔗

    Update the module documentation comment to reflect that the classification module is now implemented. Change line 40 from:

    - [`classification`]: Semantic analysis and tagging (🚧 Types defined)

    to:

    - [`classification`]: Semantic analysis and tagging (✅ Complete)

    Also add a re-export of the main classification function to the public API at the bottom of the file (after line 54) to make it easily accessible:

    pub use classification::classify_string;

    This allows users to call stringy::classify_string() directly without needing to import from the submodule.

    📄 docs/src/classification.md (MODIFY) 🔗

    Update the documentation to reflect the actual implementation:

    1. Update the implementation section (lines 92-134): Replace the example code with a note that the implementation uses std::sync::LazyLock instead of a struct-based approach, and that patterns are defined as static variables in the patterns module

    2. Add performance section: Add a new section after line 134 explaining the regex caching implementation:

      • Patterns are compiled once on first use via LazyLock
      • Zero compilation overhead after initialization
      • Thread-safe access to cached patterns
      • Typical performance: >90% reduction in classification time vs. on-demand compilation
    3. Update the regex compilation caching section (lines 348-354): Replace the lazy_static! example with std::sync::LazyLock syntax to match the actual implementation:

    use std::sync::LazyLock;
    use regex::Regex;
    
    static URL_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
        Regex::new(r"https?://[^\s]+").unwrap()
    });
    1. Add usage examples: Include practical examples of calling classify_string() with various input strings and the expected tag outputs
    📄 README.md (MODIFY) 🔗

    Update the development status section (lines 166-180) to reflect that semantic classification is now complete:

    1. Change line 176 from:
    - 🚧 **Semantic Classification**: URL, domain, path, GUID pattern matching (types defined)
    

    to:

    - ✅ **Semantic Classification**: URL, domain, path, GUID pattern matching with regex caching
    
    1. Add a bullet point in the "Current Capabilities" section (after line 193) mentioning the classification system:
    - **Semantic Classification**: Regex-based pattern matching with cached compilation for URLs, domains, IPs, file paths, registry keys, GUIDs, emails, Base64, format strings, and user agents
    
    1. Update the acknowledgements section (line 215) to confirm that regex is now actively used in the project

    Diagram

    sequenceDiagram
        participant User
        participant ClassifyString
        participant LazyLock
        participant RegexPatterns
        participant Tag
    
        User->>ClassifyString: classify_string(text)
        
        Note over ClassifyString,LazyLock: First call initializes patterns
        ClassifyString->>LazyLock: Access URL_PATTERN
        LazyLock->>RegexPatterns: Compile regex (first time only)
        RegexPatterns-->>LazyLock: Compiled Regex
        LazyLock-->>ClassifyString: Cached Regex
        
        ClassifyString->>RegexPatterns: is_match(text)
        RegexPatterns-->>ClassifyString: true/false
        
        Note over ClassifyString: Check all patterns
        ClassifyString->>LazyLock: Access DOMAIN_PATTERN
        LazyLock-->>ClassifyString: Cached Regex (instant)
        
        ClassifyString->>LazyLock: Access IPV4_PATTERN
        LazyLock-->>ClassifyString: Cached Regex (instant)
        
        ClassifyString->>LazyLock: Access GUID_PATTERN
        LazyLock-->>ClassifyString: Cached Regex (instant)
        
        Note over ClassifyString: Collect matching tags
        ClassifyString->>Tag: Create Vec<Tag>
        Tag-->>ClassifyString: tags
        
        ClassifyString-->>User: Vec<Tag>
        
        Note over User,Tag: Subsequent calls use cached patterns<br/>Zero compilation overhead
        
        User->>ClassifyString: classify_string(another_text)
        ClassifyString->>LazyLock: Access patterns (all cached)
        LazyLock-->>ClassifyString: Instant access
        ClassifyString-->>User: Vec<Tag>
    
    Loading

    Import In IDE

    VSCode Icon Cursor Icon Windsurf Icon

    🤖 Prompt for AI Agents
    I have created the following plan after thorough exploration and analysis of the codebase. Follow the below plan verbatim. Trust the files and references. Do not re-verify what's written in the plan. Explore only when absolutely necessary. First implement all the proposed file changes and then I'll review all the changes together at the end.
    
    ### Observations
    
    The codebase is well-structured with a clear separation of concerns. The `src/classification/mod.rs` module is currently empty (just a comment), providing a clean slate for implementation. The `Tag` enum is already defined in `src/types.rs` with all necessary variants. The project uses Rust 2024 edition, allowing us to use `std::sync::LazyLock` instead of external crates like `once_cell`. The documentation in `docs/src/classification.md` provides detailed pattern specifications and examples. The existing benchmark infrastructure uses criterion, and tests use insta for snapshot testing. The container parsing layer is fully implemented, so the classification module will integrate with existing `FoundString` structures.
    
    ### Approach
    
    Implement a regex compilation caching system for the semantic classifier using Rust 2024's `std::sync::LazyLock` for lazy initialization. The implementation will create a dedicated patterns module with static, lazily-initialized regex patterns for all semantic tags (URLs, domains, IPs, file paths, registry keys, GUIDs, emails, Base64, format strings, and user agents). The main classification function will use these cached patterns to tag strings efficiently. The solution includes comprehensive unit tests for each pattern and benchmarks to measure the performance improvement of caching vs. on-demand compilation.
    
    ### Reasoning
    
    I explored the project structure by listing directories recursively, then examined key files including the classification module stub, Cargo.toml dependencies, type definitions, requirements document, architecture documentation, classification design docs, existing tests, and benchmarks. This revealed that the classification module needs to be built from scratch with regex caching as a core performance feature.
    
    ## Mermaid Diagram
    
    sequenceDiagram
        participant User
        participant ClassifyString
        participant LazyLock
        participant RegexPatterns
        participant Tag
    
        User->>ClassifyString: classify_string(text)
        
        Note over ClassifyString,LazyLock: First call initializes patterns
        ClassifyString->>LazyLock: Access URL_PATTERN
        LazyLock->>RegexPatterns: Compile regex (first time only)
        RegexPatterns-->>LazyLock: Compiled Regex
        LazyLock-->>ClassifyString: Cached Regex
        
        ClassifyString->>RegexPatterns: is_match(text)
        RegexPatterns-->>ClassifyString: true/false
        
        Note over ClassifyString: Check all patterns
        ClassifyString->>LazyLock: Access DOMAIN_PATTERN
        LazyLock-->>ClassifyString: Cached Regex (instant)
        
        ClassifyString->>LazyLock: Access IPV4_PATTERN
        LazyLock-->>ClassifyString: Cached Regex (instant)
        
        ClassifyString->>LazyLock: Access GUID_PATTERN
        LazyLock-->>ClassifyString: Cached Regex (instant)
        
        Note over ClassifyString: Collect matching tags
        ClassifyString->>Tag: Create Vec<Tag>
        Tag-->>ClassifyString: tags
        
        ClassifyString-->>User: Vec<Tag>
        
        Note over User,Tag: Subsequent calls use cached patterns<br/>Zero compilation overhead
        
        User->>ClassifyString: classify_string(another_text)
        ClassifyString->>LazyLock: Access patterns (all cached)
        LazyLock-->>ClassifyString: Instant access
        ClassifyString-->>User: Vec<Tag>
    
    ## Proposed File Changes
    
    ### Cargo.toml(MODIFY)
    
    Add the `regex` dependency to the `[dependencies]` section. Use version `1.11` or later to ensure compatibility with the latest regex features and performance improvements. This is the only new dependency required since the project uses Rust 2024 edition which includes `std::sync::LazyLock` in the standard library, eliminating the need for `once_cell`.
    Add a new benchmark configuration entry in the `[[bench]]` section (after the existing `elf` benchmark at line 38-40):
    
    ```toml
    [[bench]]
    name = "regex_caching"
    harness = false
    ```
    
    This registers the new benchmark with Cargo so it can be run with `cargo bench --bench regex_caching`.
    
    ### src/classification/patterns.rs(NEW)
    
    References: 
    
    - docs/src/classification.md(MODIFY)
    
    Create a new module containing all lazily-initialized regex patterns using `std::sync::LazyLock`. Define static patterns for each semantic tag:
    
    - **URL_PATTERN**: Match HTTP/HTTPS URLs with the pattern from `docs/src/classification.md` (line 17): `https?://[^\s]+`
    - **DOMAIN_PATTERN**: Match domain names with valid TLD structure (line 24): `[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}`
    - **IPV4_PATTERN**: Match IPv4 addresses with word boundaries (line 31): `\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b`
    - **IPV6_PATTERN**: Match IPv6 addresses (line 32): `\b(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\b`
    - **FILEPATH_POSIX_PATTERN**: Match POSIX file paths (line 41): `/[^\0\n\r]*`
    - **FILEPATH_WINDOWS_PATTERN**: Match Windows file paths (line 42): `[A-Za-z]:\\[^\0\n\r]*`
    - **REGISTRY_KEY_PATTERN**: Match Windows registry paths (line 49): `HKEY_[A-Z_]+\\[^\0\n\r]*`
    - **GUID_PATTERN**: Match GUIDs with optional braces, case-insensitive (line 57): `(?i)[{]?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[}]?`
    - **EMAIL_PATTERN**: Match email addresses (line 64): `[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}`
    - **BASE64_PATTERN**: Match Base64 sequences with padding (line 80): `[A-Za-z0-9+/]{20,}={0,2}`
    - **FORMAT_STRING_PATTERN**: Match printf-style format strings (line 73): `%[sdxo]|%\d+[sdxo]|\{\d+\}`
    - **USER_AGENT_PATTERN**: Match common user agent strings (line 87): `Mozilla/[0-9.]+|Chrome/[0-9.]+|Safari/[0-9.]+`
    
    Each pattern should be wrapped in a `LazyLock<Regex>` with proper error handling using `.unwrap()` (acceptable for static patterns that are validated at compile time). Include comprehensive documentation comments explaining each pattern's purpose, examples of matches, and any edge cases.
    
    ### src/classification/mod.rs(MODIFY)
    
    References: 
    
    - src/types.rs
    - docs/src/classification.md(MODIFY)
    - docs/src/architecture.md
    
    Replace the stub comment with a complete classification implementation:
    
    1. **Module declaration**: Declare the `patterns` submodule and make it public for testing purposes
    
    2. **Import statements**: Import necessary types from `crate::types` including `Tag`, and import the patterns from the `patterns` module
    
    3. **Main classification function**: Implement `pub fn classify_string(text: &str) -> Vec<Tag>` that:
       - Takes a string slice as input
       - Returns a vector of `Tag` enums representing all matching semantic categories
       - Uses the cached regex patterns from `patterns.rs` to perform matching
       - Checks patterns in a logical order (e.g., check URL before Domain to avoid false positives)
       - Handles multiple tags per string (a string can be both a URL and contain Base64)
       - Uses `is_match()` for simple boolean checks
       - Applies the pattern matching logic described in `docs/src/classification.md` (lines 108-133)
    
    4. **Pattern matching order**: Implement the classification in this order to handle overlapping patterns correctly:
       - First check URL (most specific network indicator)
       - Then Domain (only if not already tagged as URL)
       - Check IPv4 and IPv6 patterns
       - Check file paths (Windows first, then POSIX to handle edge cases)
       - Check registry keys (Windows-specific)
       - Check GUID pattern
       - Check email addresses
       - Check Base64 sequences
       - Check format strings
       - Check user agent strings
    
    5. **Documentation**: Add comprehensive module-level documentation explaining the classification system, performance characteristics of regex caching, and usage examples
    
    6. **Helper functions**: Consider adding private helper functions for complex validation logic if needed (e.g., validating IP address ranges, checking TLD validity)
    
    The implementation should follow the architecture described in `docs/src/architecture.md` (lines 52-60) and align with the `FoundString` structure in `src/types.rs` (lines 143-163).
    Add a module declaration for the tests submodule at the end of the file:
    
    ```rust
    #[cfg(test)]
    mod tests;
    ```
    
    This follows Rust conventions for organizing unit tests in a separate file while keeping them in the same module hierarchy.
    
    ### src/classification/tests.rs(NEW)
    
    References: 
    
    - tests/integration_elf.rs
    
    Create comprehensive unit tests for the classification system:
    
    1. **Test module setup**: Use `#[cfg(test)]` and import necessary types from the parent module and `crate::types::Tag`
    
    2. **Pattern-specific tests**: Create individual test functions for each semantic tag:
       - `test_url_classification`: Test various URL formats (http, https, with paths, with parameters)
       - `test_domain_classification`: Test domain names with different TLDs, ensure URLs are not double-tagged
       - `test_ipv4_classification`: Test valid IPv4 addresses, edge cases like 0.0.0.0, 255.255.255.255
       - `test_ipv6_classification`: Test IPv6 addresses in various formats
       - `test_filepath_posix_classification`: Test POSIX paths like `/usr/bin/app`, `/etc/config`
       - `test_filepath_windows_classification`: Test Windows paths like `C:\\Windows\\System32\\file.dll`
       - `test_registry_key_classification`: Test registry paths like `HKEY_LOCAL_MACHINE\\SOFTWARE\\...`
       - `test_guid_classification`: Test GUIDs with and without braces, different cases
       - `test_email_classification`: Test valid email addresses
       - `test_base64_classification`: Test Base64 sequences with proper padding
       - `test_format_string_classification`: Test printf-style format strings like `Error: %s at line %d`
       - `test_user_agent_classification`: Test user agent strings
    
    3. **Edge case tests**:
       - `test_multiple_tags`: Verify strings can receive multiple tags (e.g., a URL containing Base64)
       - `test_no_match`: Verify plain strings without patterns return empty tag vectors
       - `test_empty_string`: Verify empty strings are handled gracefully
       - `test_special_characters`: Test strings with special regex characters
    
    4. **False positive tests**: Test strings that should NOT match:
       - Invalid IP addresses (e.g., 999.999.999.999)
       - Malformed URLs
       - Short strings that might partially match patterns
    
    5. **Performance tests**: Add tests that verify patterns are compiled only once by calling classification multiple times and ensuring consistent behavior
    
    Follow the testing patterns established in `tests/integration_elf.rs` for consistency with the project's testing style.
    
    ### benches/regex_caching.rs(NEW)
    
    References: 
    
    - benches/elf.rs
    
    Create a comprehensive benchmark suite using criterion to measure the performance improvement of regex caching:
    
    1. **Benchmark setup**: Import criterion macros (`criterion_group!`, `criterion_main!`), the classification module, and `std::hint::black_box` for preventing compiler optimizations
    
    2. **Test data preparation**: Create representative test strings for benchmarking:
       - URLs: `https://api.example.com/v1/users?id=123`
       - Domains: `malware-c2.example.com`
       - IPv4: `192.168.1.100`
       - File paths: `/usr/local/bin/application`, `C:\\Windows\\System32\\kernel32.dll`
       - Registry keys: `HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run`
       - GUIDs: `{12345678-1234-5678-1234-567812345678}`
       - Emails: `admin@example.com`
       - Format strings: `Error: %s at line %d in file %s`
       - Base64: `SGVsbG8gV29ybGQhIFRoaXMgaXMgYSB0ZXN0IHN0cmluZyBmb3IgYmVuY2htYXJraW5n`
       - User agents: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36`
       - Mixed strings with multiple patterns
       - Plain strings with no patterns
    
    3. **Benchmark functions**:
       - `bench_single_classification`: Benchmark classifying a single string (measures overhead of pattern access)
       - `bench_batch_classification_small`: Classify 100 strings (typical use case)
       - `bench_batch_classification_medium`: Classify 1,000 strings
       - `bench_batch_classification_large`: Classify 10,000 strings (stress test)
       - `bench_mixed_patterns`: Benchmark with strings that match multiple patterns
       - `bench_no_match`: Benchmark with strings that don't match any patterns (worst case)
    
    4. **Throughput measurement**: Add benchmarks that report strings classified per second to make performance improvements more tangible
    
    5. **Memory efficiency note**: Add comments explaining that LazyLock ensures patterns are compiled once and reused, with zero runtime compilation overhead after first use
    
    6. **Criterion configuration**: Use `criterion_group!` and `criterion_main!` macros following the pattern in `benches/elf.rs` (lines 66-72)
    
    The benchmarks should demonstrate the >90% reduction in classification time mentioned in the GitHub issue by showing that cached patterns eliminate regex compilation overhead.
    
    ### src/lib.rs(MODIFY)
    
    Update the module documentation comment to reflect that the classification module is now implemented. Change line 40 from:
    
    ```rust
    - [`classification`]: Semantic analysis and tagging (🚧 Types defined)
    ```
    
    to:
    
    ```rust
    - [`classification`]: Semantic analysis and tagging (✅ Complete)
    ```
    
    Also add a re-export of the main classification function to the public API at the bottom of the file (after line 54) to make it easily accessible:
    
    ```rust
    pub use classification::classify_string;
    ```
    
    This allows users to call `stringy::classify_string()` directly without needing to import from the submodule.
    
    ### docs/src/classification.md(MODIFY)
    
    Update the documentation to reflect the actual implementation:
    
    1. **Update the implementation section** (lines 92-134): Replace the example code with a note that the implementation uses `std::sync::LazyLock` instead of a struct-based approach, and that patterns are defined as static variables in the `patterns` module
    
    2. **Add performance section**: Add a new section after line 134 explaining the regex caching implementation:
       - Patterns are compiled once on first use via `LazyLock`
       - Zero compilation overhead after initialization
       - Thread-safe access to cached patterns
       - Typical performance: >90% reduction in classification time vs. on-demand compilation
    
    3. **Update the regex compilation caching section** (lines 348-354): Replace the `lazy_static!` example with `std::sync::LazyLock` syntax to match the actual implementation:
    
    ```rust
    use std::sync::LazyLock;
    use regex::Regex;
    
    static URL_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
        Regex::new(r"https?://[^\s]+").unwrap()
    });
    ```
    
    4. **Add usage examples**: Include practical examples of calling `classify_string()` with various input strings and the expected tag outputs
    
    ### README.md(MODIFY)
    
    Update the development status section (lines 166-180) to reflect that semantic classification is now complete:
    
    1. Change line 176 from:
    ```
    - 🚧 **Semantic Classification**: URL, domain, path, GUID pattern matching (types defined)
    ```
    
    to:
    ```
    - ✅ **Semantic Classification**: URL, domain, path, GUID pattern matching with regex caching
    ```
    
    2. Add a bullet point in the "Current Capabilities" section (after line 193) mentioning the classification system:
    ```
    - **Semantic Classification**: Regex-based pattern matching with cached compilation for URLs, domains, IPs, file paths, registry keys, GUIDs, emails, Base64, format strings, and user agents
    ```
    
    3. Update the acknowledgements section (line 215) to confirm that `regex` is now actively used in the project

    Developer Humor

    🧵 A regex walked into a cache,\n> *And said \I'll compile just once, with grace!*\n> LazyLock held it tight,\n> Performance took flight,\n> Now strings get tagged at a blazing pace! 🚀\n>\n> \n> _______________\n> < Cached & Fast! >\n> ---------------\n> \\ ^__^\n> \\ (oo)\\_______\n> (__)\\ )\\/\\\n> ||----w |\n> || ||\n>


    Execution Information

    Branch: main
    Commit: bdd6206


    💡 Tips

    Supported Commands (Inside Comments)

    • Use @traycerai generate to iterate on the previous version of the implementation plan.

    Supported Commands (Inside Description)

    • Add @traycerai ignore anywhere in the ticket description to prevent this ticket from being processed.
    • Add @traycerai branch:<branch-name> anywhere in the ticket description to specify the target branch for the implementation plan.

    Community

    • Join our Discord Community to get help, request features, and share feedback.
    • Follow us on X/Twitter for updates and announcements.
  7. coderabbitai commented on Nov 10, 2025

    @coderabbitai
    Contributor

    Tip

    For best results, initiate chat on the files or code changes.

    🧩 Analysis chain

    🏁 Script executed:

    #!/bin/bash
    # Fetch the current issue details
    gh issue view 33 --json title,body,labels,assignees,milestone,state,url
    

    Length of output: 1271


    🏁 Script executed:

    #!/bin/bash
    # List available labels in the repository to know what we can assign
    gh label list --limit 100
    

    Length of output: 1453


    🏁 Script executed:

    #!/bin/bash
    # Search for files related to semantic classifier and regex to understand the codebase context
    fd -e py -e js -e ts -e rb -e java semantic
    

    Length of output: 0


    🏁 Script executed:

    #!/bin/bash
    # Search for regex patterns in the codebase
    rg -n -C2 'regex|re\.compile|Regex|REGEX' --type py --type js --type ts -g '!node_modules' -g '!*.min.js' | head -100
    

    Length of output: 0


    🏁 Script executed:

    #!/bin/bash
    # Search for Rust files in the repository
    fd -e rs . | head -20
    

    Length of output: 277


    🏁 Script executed:

    #!/bin/bash
    # Search for regex/semantic patterns in Rust code
    rg -n -C3 'regex|Regex|semantic|classifier' --type rust -g '!target' | head -100
    

    Length of output: 0


    🏁 Script executed:

    #!/bin/bash
    # Look at the repository structure
    ls -la
    

    Length of output: 1765


    🏁 Script executed:

    #!/bin/bash
    # Check if there's a README or docs
    cat README.md 2>/dev/null || echo "No README.md found"
    

    Length of output: 7196


    🏁 Script executed:

    #!/bin/bash
    # Look at the classification module to understand the semantic classifier
    cat src/classification/mod.rs
    

    Length of output: 30


    🏁 Script executed:

    #!/bin/bash
    # Check Cargo.toml to see if regex crate is already used
    cat Cargo.toml
    

    Length of output: 982


    🏁 Script executed:

    #!/bin/bash
    # Look for any existing regex usage patterns in the codebase
    rg -n 'use regex' --type rust -g '!target'
    

    Length of output: 0


    🏁 Script executed:

    #!/bin/bash
    # Check the concept.md for any additional context
    cat concept.md
    

    Length of output: 6081


    🏁 Script executed:

    #!/bin/bash
    # Update the issue with enhanced content
    gh issue edit 33 \
      --title "Performance: Implement Regex Compilation Caching for Semantic Classifier" \
      --body "## Background
    
    StringyMcStringFace's semantic classifier needs to identify and tag various string patterns extracted from binaries, including:
    
    - URLs and domains
    - IP addresses (IPv4/IPv6)  
    - File paths (POSIX/Windows)
    - Registry keys
    - GUIDs
    - Email addresses
    - JWT tokens
    - Base64 sequences
    - Format strings (printf-style)
    - User agent strings
    
    Each classification requires regex pattern matching, and the classifier will process thousands of strings per binary. Without caching, regex compilation overhead becomes a significant performance bottleneck.
    
    ## Problem Statement
    
    Currently, the classification module (\`src/classification/mod.rs\`) is minimal. When implemented, it will need to compile multiple complex regex patterns. Compiling regex patterns on-demand for each string classification would:
    
    - Add significant CPU overhead (regex compilation is expensive)
    - Create unnecessary memory allocations
    - Slow down the overall analysis pipeline
    - Make the tool less suitable for batch processing
    
    ## Proposed Solution
    
    Implement a regex caching strategy using lazy initialization:
    
    ### 1. Add Dependencies
    
    Add \`regex\` and \`once_cell\` (or use std::sync::LazyLock in Rust 2024) to \`Cargo.toml\`:
    
    \`\`\`toml
    [dependencies]
    regex = \"1.11\"
    once_cell = \"1.20\"  # or use std::sync::LazyLock
    \`\`\`
    
    ### 2. Implement Lazy-Initialized Regex Cache
    
    Create a module with static, lazily-initialized regex patterns:
    
    \`\`\`rust
    use once_cell::sync::Lazy;
    use regex::Regex;
    
    pub struct PatternCache {
        pub url: &'static Lazy<Regex>,
        pub domain: &'static Lazy<Regex>,
        pub ipv4: &'static Lazy<Regex>,
        pub ipv6: &'static Lazy<Regex>,
        pub filepath_posix: &'static Lazy<Regex>,
        pub filepath_windows: &'static Lazy<Regex>,
        pub registry_key: &'static Lazy<Regex>,
        pub guid: &'static Lazy<Regex>,
        pub email: &'static Lazy<Regex>,
        pub base64: &'static Lazy<Regex>,
        pub format_string: &'static Lazy<Regex>,
        pub user_agent: &'static Lazy<Regex>,
    }
    
    static URL_PATTERN: Lazy<Regex> = Lazy::new(|| {
        Regex::new(r\"https?://[\\w.-]+(?:/[\\w./?%&=-]*)?\").unwrap()
    });
    
    static GUID_PATTERN: Lazy<Regex> = Lazy::new(|| {
        Regex::new(r\"(?i)[{]?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[}]?\").unwrap()
    });
    
    // ... additional patterns
    \`\`\`
    
    ### 3. Use Cached Patterns in Classifier
    
    The semantic classifier should reference these static patterns:
    
    \`\`\`rust
    pub fn classify_string(text: &str) -> Vec<Tag> {
        let mut tags = Vec::new();
        
        if URL_PATTERN.is_match(text) {
            tags.push(Tag::Url);
        }
        if GUID_PATTERN.is_match(text) {
            tags.push(Tag::Guid);
        }
        // ... additional classifications
        
        tags
    }
    \`\`\`
    
    ### 4. Consider RegexSet for Multiple Patterns
    
    For optimal performance when checking multiple patterns against the same text, consider using \`RegexSet\`:
    
    \`\`\`rust
    use regex::RegexSet;
    
    static PATTERN_SET: Lazy<RegexSet> = Lazy::new(|| {
        RegexSet::new(&[
            r\"https?://.*\",  // URL
            r\"[{]?[0-9a-f]{8}-.*\",  // GUID
            // ... more patterns
        ]).unwrap()
    });
    \`\`\`
    
    ## Implementation Tasks
    
    - [ ] Add \`regex\` dependency to \`Cargo.toml\`
    - [ ] Add \`once_cell\` or use \`std::sync::LazyLock\` for lazy initialization
    - [ ] Create \`src/classification/patterns.rs\` with cached regex patterns
    - [ ] Implement pattern definitions for all semantic tags
    - [ ] Create \`classify_string()\` function using cached patterns
    - [ ] Add unit tests for each pattern
    - [ ] Create benchmark suite to measure performance improvement
    - [ ] Document pattern syntax and matching behavior
    
    ## Success Criteria
    
    - All regex patterns compiled once on first use
    - Zero regex compilation overhead during string classification
    - Measurable performance improvement (target: >90% reduction in classification time)
    - Comprehensive test coverage for all patterns
    - Benchmarks demonstrating caching effectiveness
    
    ## Performance Benchmarks
    
    Create benchmarks in \`benches/regex_caching.rs\` to measure:
    
    1. **Compilation overhead**: Time to compile patterns with/without caching
    2. **Classification throughput**: Strings classified per second
    3. **Memory usage**: Compare cached vs. on-demand compilation
    4. **Batch processing**: Time to classify 10k, 100k, 1M strings
    
    Expected improvement: 10-100x faster classification depending on pattern complexity and string volume.
    
    ## References
    
    - Rust regex crate documentation: https://docs.rs/regex/latest/regex/
    - Performance tips: https://docs.rs/regex/latest/regex/index.html#performance
    - once_cell for lazy statics: https://docs.rs/once_cell/latest/once_cell/
    
    ## Related
    
    - Part of v0.1 MVP milestone
    - Blocks efficient implementation of semantic classification (requirement 8.3)
    - Required for achieving acceptable performance on large binaries"
    

    Length of output: 61


    🏁 Script executed:

    #!/bin/bash
    # Add priority:medium and MVP labels to the issue
    gh issue edit 33 --add-label "priority:medium,MVP"
    

    Length of output: 61


    🏁 Script executed:

    #!/bin/bash
    # Verify the updated issue details
    gh issue view 33 --json title,body,labels,assignees,milestone,state,url
    

    Length of output: 6149


    @unclesp1d3r I've successfully enhanced issue #33 with comprehensive improvements:

    ✅ What Was Updated

    Title

    • Before: "Regex Caching"
    • After: "Performance: Implement Regex Compilation Caching for Semantic Classifier"

    Issue Body Enhancements

    Added comprehensive sections including:

    • Background: Detailed context about the semantic classifier's pattern matching requirements (URLs, GUIDs, file paths, etc.)
    • Problem Statement: Clear explanation of why regex compilation overhead is a bottleneck
    • Proposed Solution: Four-part implementation strategy with code examples:
      1. Adding dependencies (regex and once_cell)
      2. Implementing lazy-initialized regex cache patterns
      3. Using cached patterns in the classifier
      4. Considering RegexSet for optimal performance
    • Implementation Tasks: 8-item checklist for execution
    • Success Criteria: Measurable goals including >90% performance improvement target
    • Performance Benchmarks: Specific metrics to track (compilation overhead, throughput, memory, batch processing)
    • References: Links to regex crate documentation and performance tips
    • Related Context: Connection to MVP milestone and requirement 8.3

    Labels Added

    • ✅ MVP - Since this is part of the v0.1 milestone
    • ✅ priority:medium - Performance optimization for core functionality

    Existing Metadata Preserved

    • Assignee: unclesp1d3r
    • Milestone: v0.1
    • Labels: area:analyzer, type:enhancement, status:backlog, lang:rust, needs:tests

    The issue is now much more actionable with clear implementation guidance, concrete code examples, and measurable success criteria. The enhanced title improves discoverability and clearly communicates the performance focus.

  8. unclesp1d3r commented on Jul 4, 2026

    @unclesp1d3r
    MemberAuthor

    Implemented: regexes are compiled once via std::sync::LazyLock across src/classification/patterns/. Closing as completed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions