Skip to main content

Overview

The PassContext class provides shared state and utilities that all obfuscation passes can access. It contains the symbol table for tracking name mappings, a name generator for creating unique identifiers, configuration options, and scope resolution logic. Source: lib/src/engine/runner/pass_context.dart:10

Constructor

PassContext({
  required SymbolTable symbolTable,
  required NameGenerator nameGenerator,
  required PassOptions options,
})
symbolTable
SymbolTable
required
Tracks original → obfuscated name mappings for debugging
nameGenerator
NameGenerator
required
Generates unique sequential obfuscated identifiers
options
PassOptions
required
Configuration options (exclusions, preserve_main, verbose, etc.)
The PassContext automatically detects the project root and package name from pubspec.yaml when constructed. This is used by shouldObfuscateLibrary() to determine scope.

Properties

symbolTable

final SymbolTable symbolTable
symbolTable
SymbolTable
Shared symbol table for recording original → obfuscated name mappings. All passes can add entries here.

nameGenerator

final NameGenerator nameGenerator
nameGenerator
NameGenerator
Shared name generator. Passes should call nameGenerator.next() to get unique obfuscated identifiers.

options

final PassOptions options
options
PassOptions
Configuration options including exclusion patterns, preserve_main, string encryption settings, and verbose logging.

Methods

shouldObfuscateLibrary()

bool shouldObfuscateLibrary(Library library)
Determines whether a given library belongs to user code that should be obfuscated.
library
Library
required
The kernel Library object to check
return
bool
true if the library should be obfuscated, false otherwise
Returns false for:
  • SDK libraries (dart:*)
  • External packages (package URIs not matching the current project)
  • Libraries matching exclusion glob patterns in options.excludeLibraryUriPatterns
Returns true for:
  • Libraries in the current project’s package (package:my_app/*)
  • File-based libraries in the current directory
Example:
@override
void run(Component component, PassContext context) {
  for (final library in component.libraries) {
    if (!context.shouldObfuscateLibrary(library)) {
      continue; // Skip this library
    }
    
    // Safe to obfuscate—this is user code
    for (final klass in library.classes) {
      // Transform classes...
    }
  }
}

Scope Detection

PassContext automatically detects obfuscation scope:
  1. Project package detection:
    • Reads pubspec.yaml from the current working directory
    • Extracts the name field to identify the project package
    • Example: name: my_app → only package:my_app/* is obfuscated
  2. File-based libraries:
    • Detects the project root path
    • Includes all file: URIs under the project directory
  3. Exclusion patterns:
    • Applies glob patterns from options.excludeLibraryUriPatterns
    • Common patterns: **/*.g.dart, **/*.freezed.dart
This automatic scope detection ensures that only your code is obfuscated—external dependencies and SDK libraries are never touched, preventing runtime errors.

Usage Example

import 'package:kernel/kernel.dart';
import 'package:refractor/refractor.dart';

class MyPass implements Pass {
  @override
  String get name => 'my_pass';

  @override
  void run(Component component, PassContext context) {
    for (final library in component.libraries) {
      // Check scope
      if (!context.shouldObfuscateLibrary(library)) continue;

      for (final klass in library.classes) {
        final originalName = klass.name;
        
        // Generate obfuscated name
        final obfuscatedName = context.nameGenerator.next();
        
        // Record mapping
        context.symbolTable.record(originalName, obfuscatedName);
        
        // Apply transformation
        klass.name = obfuscatedName;

        // Verbose logging
        if (context.options.verbose) {
          print('Renamed $originalName -> $obfuscatedName');
        }
      }
    }
  }
}

Integration with PassRunner

The PassRunner creates a single PassContext instance and passes it to all configured passes:
final context = PassContext(
  symbolTable: SymbolTable(),
  nameGenerator: NameGenerator(),
  options: PassOptions(
    excludeLibraryUriPatterns: [Glob('**/*.g.dart')],
    preserveMain: true,
    verbose: true,
  ),
);

for (final pass in passes) {
  pass.run(component, context); // Same context for all passes
}
This ensures:
  • All passes share the same symbol table (consistent mappings)
  • Name generation is sequential across all passes (no collisions)
  • Configuration is consistent

See Also

Pass Interface

Base class that receives PassContext

PassRunner

Creates and manages PassContext

PassOptions

Configuration options in context

SymbolTable

Symbol table in context

NameGenerator

Name generator in context

Build docs developers (and LLMs) love