Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Pointcut Expressions

This chapter explains the pointcut expression language used in Phase 3 for automatic aspect matching, including syntax, semantics, and advanced patterns.

What Are Pointcuts?

Pointcuts are expressions that select join points (function calls) where aspects should be applied:

Pointcut Expression → Selects Functions → Aspects Applied

Example:

--aspect-pointcut "execution(pub fn *(..))"
# Selects all public functions

Pointcut Syntax

Execution Pointcut

Matches function execution based on signature:

execution(VISIBILITY fn NAME(PARAMETERS))

Components:

  • VISIBILITY: pub, pub(crate), or omit for any
  • fn: Keyword (required)
  • NAME: Function name or * wildcard
  • PARAMETERS: (..) for any parameters

Examples:

# All public functions
execution(pub fn *(..))

# Specific function
execution(pub fn fetch_user(..))

# All functions (any visibility)
execution(fn *(..))

# Private functions
execution(fn *(..) where !pub)

Within Pointcut

Matches functions within a specific module:

within(MODULE_PATH)

Examples:

# All functions in api module
within(api)

# Nested module
within(api::handlers)

# Full path
within(crate::api)

Call Pointcut

Matches function calls (caller perspective):

call(FUNCTION_NAME)

Examples:

# Any call to database::query
call(database::query)

# Any call to functions starting with "fetch_"
call(fetch_*)

Pattern Matching

Wildcard Matching

Use * to match any name:

# All functions
execution(fn *(..))

# All functions starting with "get_"
execution(fn get_*(..))

# All functions in any submodule
within(*::handlers)

Visibility Matching

# Public functions
execution(pub fn *(..))

# Crate-visible functions
execution(pub(crate) fn *(..))

# Private functions (no visibility keyword)
execution(fn *(..) where !pub)

Module Path Matching

# Exact module
within(api)

# Module prefix
within(api::*)

# Nested modules
within(crate::api::handlers)

Implementation Details

Pointcut Data Structure

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, PartialEq)]
pub enum Pointcut {
    Execution(ExecutionPattern),
    Within(String),
    Call(String),
    And(Box<Pointcut>, Box<Pointcut>),
    Or(Box<Pointcut>, Box<Pointcut>),
    Not(Box<Pointcut>),
}

#[derive(Debug, Clone, PartialEq)]
pub struct ExecutionPattern {
    pub visibility: Option<VisibilityKind>,
    pub name: String,          // "*" for wildcard
    pub async_fn: Option<bool>,
}
}

Parsing Execution Patterns

#![allow(unused)]
fn main() {
impl Pointcut {
    pub fn parse_execution(expr: &str) -> Result<Self, ParseError> {
        // expr = "execution(pub fn *(..))
        
        // Remove "execution(" and trailing ")"
        let inner = expr
            .strip_prefix("execution(")
            .and_then(|s| s.strip_suffix(")"))
            .ok_or(ParseError::InvalidSyntax)?;
        
        // Parse: "pub fn *(..)"
        let parts: Vec<&str> = inner.split_whitespace().collect();
        
        let mut visibility = None;
        let mut name = "*".to_string();
        let mut async_fn = None;
        
        let mut i = 0;
        
        // Check for visibility
        if parts.get(i) == Some(&"pub") {
            visibility = Some(VisibilityKind::Public);
            i += 1;
            
            // Check for pub(crate)
            if parts.get(i).map(|s| s.starts_with("(crate)")).unwrap_or(false) {
                visibility = Some(VisibilityKind::Crate);
                i += 1;
            }
        }
        
        // Check for async
        if parts.get(i) == Some(&"async") {
            async_fn = Some(true);
            i += 1;
        }
        
        // Expect "fn"
        if parts.get(i) != Some(&"fn") {
            return Err(ParseError::MissingFnKeyword);
        }
        i += 1;
        
        // Get function name
        if let Some(name_part) = parts.get(i) {
            // Remove trailing "(..)" if present
            name = name_part.trim_end_matches("(..)").to_string();
        }
        
        Ok(Pointcut::Execution(ExecutionPattern {
            visibility,
            name,
            async_fn,
        }))
    }
}
}

Matching Algorithm

#![allow(unused)]
fn main() {
impl PointcutMatcher {
    pub fn matches(&self, pointcut: &Pointcut, func: &FunctionMetadata) -> bool {
        match pointcut {
            Pointcut::Execution(pattern) => {
                self.matches_execution(pattern, func)
            }
            Pointcut::Within(module) => {
                self.matches_within(module, func)
            }
            Pointcut::Call(name) => {
                self.matches_call(name, func)
            }
            Pointcut::And(p1, p2) => {
                self.matches(p1, func) && self.matches(p2, func)
            }
            Pointcut::Or(p1, p2) => {
                self.matches(p1, func) || self.matches(p2, func)
            }
            Pointcut::Not(p) => {
                !self.matches(p, func)
            }
        }
    }
    
    fn matches_execution(
        &self,
        pattern: &ExecutionPattern,
        func: &FunctionMetadata
    ) -> bool {
        // Check visibility
        if let Some(required_vis) = &pattern.visibility {
            if &func.visibility != required_vis {
                return false;
            }
        }
        
        // Check async
        if let Some(required_async) = pattern.async_fn {
            if func.is_async != required_async {
                return false;
            }
        }
        
        // Check name
        if pattern.name != "*" {
            if !self.matches_name(&pattern.name, &func.simple_name) {
                return false;
            }
        }
        
        true
    }
    
    fn matches_name(&self, pattern: &str, name: &str) -> bool {
        if pattern == "*" {
            return true;
        }
        
        // Wildcard matching
        if pattern.ends_with("*") {
            let prefix = pattern.trim_end_matches('*');
            return name.starts_with(prefix);
        }
        
        if pattern.starts_with("*") {
            let suffix = pattern.trim_start_matches('*');
            return name.ends_with(suffix);
        }
        
        // Exact match
        pattern == name
    }
    
    fn matches_within(&self, module: &str, func: &FunctionMetadata) -> bool {
        // Check if function is within specified module
        
        // Handle wildcard
        if module.ends_with("::*") {
            let prefix = module.trim_end_matches("::*");
            return func.module_path.starts_with(prefix);
        }
        
        // Exact match or prefix match
        func.module_path == module ||
        func.module_path.starts_with(&format!("{}::", module)) ||
        func.qualified_name.contains(&format!("::{}", module))
    }
}
}

Boolean Combinators

Combine pointcuts with logical operators:

AND Combinator

# Public functions in api module
execution(pub fn *(..)) && within(api)

Implementation:

#![allow(unused)]
fn main() {
Pointcut::And(
    Box::new(Pointcut::Execution(/* pub fn *(..) */)),
    Box::new(Pointcut::Within("api".to_string())),
)
}

OR Combinator

# Functions in api or handlers modules
within(api) || within(handlers)

Implementation:

#![allow(unused)]
fn main() {
Pointcut::Or(
    Box::new(Pointcut::Within("api".to_string())),
    Box::new(Pointcut::Within("handlers".to_string())),
)
}

NOT Combinator

# All functions except in tests module
execution(fn *(..)) && !within(tests)

Implementation:

#![allow(unused)]
fn main() {
Pointcut::And(
    Box::new(Pointcut::Execution(/* fn *(..) */)),
    Box::new(Pointcut::Not(
        Box::new(Pointcut::Within("tests".to_string())),
    )),
)
}

Practical Examples

Example 1: API Logging

Apply logging to all public API functions:

aspect-rustc-driver \
    --aspect-pointcut "execution(pub fn *(..)) && within(api)" \
    --aspect-apply "LoggingAspect::new()"

Matches:

#![allow(unused)]
fn main() {
// ✓ Matched
pub mod api {
    pub fn fetch_user(id: u64) -> User { }
    pub fn save_user(user: User) -> Result<()> { }
}

// ✗ Not matched (not in api module)
pub fn helper() { }

// ✗ Not matched (private)
mod api {
    fn internal() { }
}
}

Example 2: Async Function Timing

Time all async functions:

aspect-rustc-driver \
    --aspect-pointcut "execution(pub async fn *(..))" \
    --aspect-apply "TimingAspect::new()"

Matches:

#![allow(unused)]
fn main() {
// ✓ Matched
pub async fn fetch_data() -> Data { }

// ✗ Not matched (not async)
pub fn sync_function() { }

// ✗ Not matched (private)
async fn private_async() { }
}

Example 3: Database Transaction Management

Apply transactions to all database operations:

aspect-rustc-driver \
    --aspect-pointcut "within(database::ops)" \
    --aspect-apply "TransactionalAspect::new()"

Matches:

#![allow(unused)]
fn main() {
// ✓ Matched
mod database {
    mod ops {
        pub fn insert(data: Data) -> Result<()> { }
        fn delete(id: u64) -> Result<()> { }  // Also matched
    }
}

// ✗ Not matched
mod database {
    pub fn connect() -> Connection { }
}
}

Example 4: Security for Admin Functions

Apply authorization to admin functions:

aspect-rustc-driver \
    --aspect-pointcut "execution(pub fn admin_*(..))" \
    --aspect-apply "AuthorizationAspect::require_role(\"admin\")"

Matches:

#![allow(unused)]
fn main() {
// ✓ Matched
pub fn admin_delete_user(id: u64) { }
pub fn admin_grant_permissions(user: User) { }

// ✗ Not matched
pub fn user_profile() { }
pub fn admin() { }  // Exact match, not prefix
}

Example 5: Exclude Test Code

Apply aspects to all code except tests:

aspect-rustc-driver \
    --aspect-pointcut "execution(pub fn *(..)) && !within(tests)" \
    --aspect-apply "MetricsAspect::new()"

Matches:

#![allow(unused)]
fn main() {
// ✓ Matched
pub fn production_code() { }

mod api {
    pub fn handler() { }  // ✓ Matched
}

// ✗ Not matched
mod tests {
    pub fn test_something() { }
}
}

Command-Line Usage

Single Pointcut

aspect-rustc-driver \
    --aspect-pointcut "execution(pub fn *(..))" \
    main.rs

Multiple Pointcuts

aspect-rustc-driver \
    --aspect-pointcut "execution(pub fn *(..))" \
    --aspect-pointcut "within(api)" \
    --aspect-pointcut "within(handlers)" \
    main.rs

Each pointcut is evaluated independently.

With Aspect Application

aspect-rustc-driver \
    --aspect-pointcut "execution(pub fn *(..))" \
    --aspect-apply "LoggingAspect::new()" \
    main.rs

Configuration File

Create aspect-config.toml:

[[pointcuts]]
pattern = "execution(pub fn *(..))"
aspects = ["LoggingAspect::new()"]

[[pointcuts]]
pattern = "within(api)"
aspects = ["TimingAspect::new()", "SecurityAspect::new()"]

[options]
verbose = true
output = "target/aspect-analysis.txt"

Use with:

aspect-rustc-driver --aspect-config aspect-config.toml main.rs

Advanced Patterns

Combining Multiple Criteria

# Public async functions in api module, except tests
execution(pub async fn *(..)) && within(api) && !within(api::tests)

Prefix and Suffix Matching

# Functions starting with "get_"
execution(fn get_*(..))

# Functions ending with "_handler"
execution(fn *_handler(..))

Module Hierarchies

# All submodules of api
within(api::*)

# Specific nested module
within(crate::services::api::handlers)

Visibility Variants

# Public and crate-visible
execution(pub fn *(..)) || execution(pub(crate) fn *(..))

# Only truly public
execution(pub fn *(..)) && !execution(pub(crate) fn *(..))

Pointcut Library

Common pointcut patterns for reuse:

All Public API

--aspect-pointcut "execution(pub fn *(..)) && (within(api) || within(handlers))"

All Database Operations

--aspect-pointcut "within(database) || call(query) || call(execute)"

All HTTP Handlers

--aspect-pointcut "execution(pub async fn *_handler(..))"

All Admin Functions

--aspect-pointcut "execution(pub fn admin_*(..)) || within(admin)"

Production Code Only

--aspect-pointcut "execution(fn *(..)) && !within(tests) && !within(benches)"

Performance Considerations

Pointcut Evaluation Cost

#![allow(unused)]
fn main() {
// Fast: Simple checks
execution(pub fn *(..))          // O(1) visibility check

// Medium: String matching
execution(fn get_*(..))          // O(n) prefix check

// Slow: Complex combinators
(execution(...) && within(...)) || (!execution(...))  // Multiple checks
}

Optimization strategy:

  • Evaluate cheapest checks first
  • Short-circuit on failure
  • Cache results when possible

Compilation Impact

Simple pointcut:    +1% compile time
Complex pointcut:   +3% compile time
Multiple pointcuts: +2% per pointcut

Still negligible compared to total compilation.

Testing Pointcuts

Dry Run Mode

aspect-rustc-driver \
    --aspect-pointcut "execution(pub fn *(..))" \
    --aspect-dry-run \
    --aspect-output matches.txt \
    main.rs

Outputs matched functions without applying aspects.

Verification

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_execution_pointcut() {
        let pointcut = Pointcut::parse_execution("execution(pub fn *(..))").unwrap();
        let func = FunctionMetadata {
            simple_name: "test_func".to_string(),
            visibility: VisibilityKind::Public,
            // ...
        };
        
        let matcher = PointcutMatcher::new(vec![pointcut]);
        assert!(matcher.matches(&pointcut, &func));
    }
    
    #[test]
    fn test_within_pointcut() {
        let pointcut = Pointcut::Within("api".to_string());
        let func = FunctionMetadata {
            module_path: "crate::api".to_string(),
            // ...
        };
        
        let matcher = PointcutMatcher::new(vec![pointcut]);
        assert!(matcher.matches(&pointcut, &func));
    }
}
}

Error Handling

Invalid Syntax

$ aspect-rustc-driver --aspect-pointcut "invalid syntax"

error: Failed to parse pointcut expression
  --> invalid syntax
   |
   | Expected: execution(PATTERN) or within(MODULE)

Missing Components

$ aspect-rustc-driver --aspect-pointcut "execution(*(..))

error: Missing 'fn' keyword in execution pointcut
  --> execution(*(..))
   |
   | Expected: execution([pub] fn NAME(..))

Unsupported Features

$ aspect-rustc-driver --aspect-pointcut "args(i32, String)"

error: 'args' pointcut not yet supported
  --> Use execution(...) or within(...) instead

Future Enhancements

Planned Features

  1. Parameter Matching

    execution(fn *(id: u64, ..))
    
  2. Return Type Matching

    execution(fn *(..) -> Result<T, E>)
    
  3. Annotation Matching

    execution(@deprecated fn *(..))
    
  4. Call-Site Matching

    call(database::query) && within(api)
    
  5. Field Access

    get(User.email) || set(User.*)
    

Key Takeaways

  1. Pointcuts select functions automatically - No manual annotations
  2. Three main types - execution, within, call
  3. Wildcards enable flexible matching - * matches anything
  4. Boolean combinators - AND, OR, NOT for complex logic
  5. Compile-time evaluation - Zero runtime cost
  6. Extensible design - Easy to add new pointcut types
  7. Production-ready - Handles real Rust code

Next Steps


Related Chapters: