Class: Taski::StaticAnalysis::StartDepAnalyzer

Inherits:
Object
  • Object
show all
Defined in:
lib/taski/static_analysis/start_dep_analyzer.rb

Overview

Analyzes a task's run method AST to find dependencies that are safe to speculatively pre-start (start_dep). Uses a whitelist approach: only confirmed patterns are collected; unknown patterns cause the analyzer to stop (returning what was collected so far up to that point).

Currently handles variable assignment patterns only:

a = Dep.value        (LocalVariableWriteNode)
@a = Dep.value       (InstanceVariableWriteNode)

This is a performance optimization only — if analysis fails or returns empty, tasks still work correctly via lazy Fiber pull (need_dep).

Defined Under Namespace

Classes: AnalysisResult, DepInfo

Constant Summary collapse

SAFE_TYPES =

AST node types that are known safe (not dependencies, won't stop scanning)

Set[
  Prism::LocalVariableReadNode, Prism::InstanceVariableReadNode,
  Prism::ConstantReadNode, Prism::ConstantPathNode,
  Prism::IntegerNode, Prism::FloatNode, Prism::StringNode,
  Prism::SymbolNode, Prism::NilNode, Prism::TrueNode, Prism::FalseNode,
  Prism::SelfNode
].freeze
EMPTY_RESULT =
AnalysisResult.new(start_deps: Set.new.freeze, sync_deps: Set.new.freeze).freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeStartDepAnalyzer

Returns a new instance of StartDepAnalyzer.



59
60
61
62
# File 'lib/taski/static_analysis/start_dep_analyzer.rb', line 59

def initialize
  @deps = []
  @seen_classes = Set.new
end

Class Method Details

.analyze(task_class) ⇒ Array<DepInfo>

Analyze a task class and return deps safe to prestart. Results are cached per task class.

Parameters:

  • task_class (Class)

    The task class to analyze

Returns:

  • (Array<DepInfo>)

    Deduplicated list of safe dependencies



39
40
41
42
43
44
45
46
47
48
49
# File 'lib/taski/static_analysis/start_dep_analyzer.rb', line 39

def analyze(task_class)
  @cache_mutex.synchronize do
    return @cache[task_class] if @cache.key?(task_class)
  end

  result = new.analyze(task_class)

  @cache_mutex.synchronize do
    @cache[task_class] ||= result
  end
end

.clear_cache!Object

Clear cache (for testing)



52
53
54
# File 'lib/taski/static_analysis/start_dep_analyzer.rb', line 52

def clear_cache!
  @cache_mutex.synchronize { @cache.clear }
end

Instance Method Details

#analyze(task_class) ⇒ AnalysisResult

Analyze a task class's run method and return safe-to-prestart deps and sync_dep_classes (deps whose proxy variables are used unsafely).

Parameters:

  • task_class (Class)

    The task class to analyze

Returns:



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/taski/static_analysis/start_dep_analyzer.rb', line 68

def analyze(task_class)
  @task_class = task_class
  @exported_ivars = Set.new(task_class.exported_methods.map { |m| :"@#{m}" })
  source_location = task_class.instance_method(:run).source_location
  return EMPTY_RESULT unless source_location

  file_path, _line = source_location
  parse_result = Prism.parse_file(file_path)

  run_node = find_run_method(parse_result.value, task_class)
  return EMPTY_RESULT unless run_node&.body

  scan_statements(run_node.body)
  unsafe_classes = detect_unsafe_proxy_usage(run_node.body)
  all_dep_classes = Set.new(@deps.map(&:klass))
  start_deps = all_dep_classes - unsafe_classes
  sync_deps = unsafe_classes
  AnalysisResult.new(start_deps: start_deps, sync_deps: sync_deps)
rescue NameError
  EMPTY_RESULT
end