• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nshkrdotcom / ElixirScope / 671f6c2f62938b306383bc0eb80a3fa9d3d150e6

28 May 2025 06:03PM UTC coverage: 59.97% (-0.05%) from 60.023%
671f6c2f62938b306383bc0eb80a3fa9d3d150e6

push

github

NSHkr
CPG formalization plans

5675 of 9463 relevant lines covered (59.97%)

3796.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.16
/lib/elixir_scope/ast_repository/runtime_correlator.ex
1
defmodule ElixirScope.ASTRepository.RuntimeCorrelator do
2
  @moduledoc """
3
  AST-Runtime Correlator for the Enhanced AST Repository.
4
  
5
  Provides seamless correlation between static AST analysis and runtime behavior,
6
  enabling revolutionary debugging features:
7
  
8
  - **Structural Breakpoints**: Break on AST patterns during execution
9
  - **Data Flow Breakpoints**: Break when variables flow through specific AST paths
10
  - **Semantic Watchpoints**: Track variables through AST structure, not just scope
11
  - **AST-Aware Execution Traces**: Show code structure during execution replay
12
  
13
  ## Performance Targets
14
  
15
  - Event correlation: <1ms per event
16
  - AST context lookup: <10ms
17
  - Runtime query enhancement: <50ms
18
  - Memory overhead: <10% of base EventStore
19
  
20
  ## Integration Points
21
  
22
  - EventStore: ast_node_id correlation
23
  - InstrumentationRuntime: Enhanced event capture
24
  - Query Engine: Runtime-aware query optimization
25
  - Temporal Bridge: AST-enhanced state reconstruction
26
  
27
  ## Examples
28
  
29
      # Correlate runtime event to AST
30
      {:ok, ast_context} = RuntimeCorrelator.correlate_event_to_ast(repo, event)
31
      
32
      # Get AST context for runtime event
33
      {:ok, context} = RuntimeCorrelator.get_runtime_context(repo, event)
34
      
35
      # Enhance event with AST metadata
36
      {:ok, enhanced_event} = RuntimeCorrelator.enhance_event_with_ast(repo, event)
37
      
38
      # Build AST-aware execution trace
39
      {:ok, trace} = RuntimeCorrelator.build_execution_trace(repo, events)
40
  """
41
  
42
  use GenServer
43
  require Logger
44
  
45
  alias ElixirScope.ASTRepository.EnhancedRepository
46
  alias ElixirScope.ASTRepository.Enhanced.{
47
    EnhancedFunctionData,
48
    EnhancedModuleData,
49
    CFGData,
50
    DFGData
51
  }
52
  alias ElixirScope.Storage.EventStore
53
  alias ElixirScope.Events
54
  
55
  @table_name :runtime_correlator_main
56
  @context_cache :runtime_correlator_context_cache
57
  @trace_cache :runtime_correlator_trace_cache
58
  
59
  # Performance targets
60
  @correlation_timeout 5000
61
  @context_lookup_timeout 5000
62
  @query_enhancement_timeout 5000
63
  
64
  # Cache TTL (5 minutes)
65
  @cache_ttl 300_000
66
  
67
  defstruct [
68
    :ast_repo,
69
    :event_store,
70
    :correlation_stats,
71
    :cache_stats,
72
    :breakpoints,
73
    :watchpoints
74
  ]
75
  
76
  @type ast_context :: %{
77
    module: atom(),
78
    function: atom(),
79
    arity: non_neg_integer(),
80
    ast_node_id: String.t(),
81
    line_number: pos_integer(),
82
    ast_metadata: map(),
83
    cfg_node: map() | nil,
84
    dfg_context: map() | nil,
85
    variable_scope: map(),
86
    call_context: list(map())
87
  }
88
  
89
  @type enhanced_event :: %{
90
    original_event: map(),
91
    ast_context: ast_context() | nil,
92
    correlation_metadata: map(),
93
    structural_info: map(),
94
    data_flow_info: map()
95
  }
96
  
97
  @type execution_trace :: %{
98
    events: list(enhanced_event()),
99
    ast_flow: list(map()),
100
    variable_flow: map(),
101
    structural_patterns: list(map()),
102
    performance_correlation: map(),
103
    trace_metadata: map()
104
  }
105
  
106
  @type structural_breakpoint :: %{
107
    id: String.t(),
108
    pattern: Macro.t(),
109
    condition: atom(),
110
    ast_path: list(String.t()),
111
    enabled: boolean(),
112
    hit_count: non_neg_integer(),
113
    metadata: map()
114
  }
115
  
116
  @type data_flow_breakpoint :: %{
117
    id: String.t(),
118
    variable: String.t(),
119
    ast_path: list(String.t()),
120
    flow_conditions: list(atom()),
121
    enabled: boolean(),
122
    hit_count: non_neg_integer(),
123
    metadata: map()
124
  }
125
  
126
  @type semantic_watchpoint :: %{
127
    id: String.t(),
128
    variable: String.t(),
129
    track_through: list(atom()),
130
    ast_scope: String.t(),
131
    enabled: boolean(),
132
    value_history: list(map()),
133
    metadata: map()
134
  }
135
  
136
  # GenServer API
137
  
138
  def start_link(opts \\ []) do
139
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
30✔
140
  end
141
  
142
  def init(opts) do
143
    # Create ETS tables for correlation and caching (handle existing tables gracefully)
144
    try do
30✔
145
      :ets.new(@table_name, [:named_table, :public, :set, {:read_concurrency, true}])
30✔
146
    rescue
147
      ArgumentError -> 
148
        # Table already exists, clear it
149
        :ets.delete_all_objects(@table_name)
×
150
    end
151
    
152
    try do
30✔
153
      :ets.new(@context_cache, [:named_table, :public, :set, {:read_concurrency, true}])
30✔
154
    rescue
155
      ArgumentError -> 
156
        # Table already exists, clear it
157
        :ets.delete_all_objects(@context_cache)
×
158
    end
159
    
160
    try do
30✔
161
      :ets.new(@trace_cache, [:named_table, :public, :set, {:read_concurrency, true}])
30✔
162
    rescue
163
      ArgumentError -> 
164
        # Table already exists, clear it
165
        :ets.delete_all_objects(@trace_cache)
×
166
    end
167
    
168
    state = %__MODULE__{
30✔
169
      ast_repo: Keyword.get(opts, :ast_repo),
170
      event_store: Keyword.get(opts, :event_store),
171
      correlation_stats: %{
172
        events_correlated: 0,
173
        context_lookups: 0,
174
        cache_hits: 0,
175
        cache_misses: 0
176
      },
177
      cache_stats: %{
178
        context_cache_size: 0,
179
        trace_cache_size: 0,
180
        evictions: 0
181
      },
182
      breakpoints: %{
183
        structural: %{},
184
        data_flow: %{},
185
        semantic: %{}
186
      },
187
      watchpoints: %{}
188
    }
189
    
190
    Logger.info("RuntimeCorrelator started with AST-Runtime integration")
30✔
191
    {:ok, state}
192
  end
193
  
194
  # Public API
195
  
196
  @doc """
197
  Correlates a runtime event to precise AST nodes.
198
  
199
  Links runtime events to their corresponding AST structure, enabling
200
  structural debugging and analysis.
201
  
202
  ## Parameters
203
  
204
  - `repo` - The Enhanced AST Repository
205
  - `event` - Runtime event to correlate
206
  
207
  ## Returns
208
  
209
  - `{:ok, ast_context}` - AST context for the event
210
  - `{:error, reason}` - Correlation failed
211
  
212
  ## Examples
213
  
214
      event = %Events.FunctionEntry{
215
        module: MyModule,
216
        function: :my_function,
217
        arity: 2,
218
        correlation_id: "abc123"
219
      }
220
      
221
      {:ok, context} = RuntimeCorrelator.correlate_event_to_ast(repo, event)
222
      # context.ast_node_id => "MyModule.my_function/2:line_15"
223
  """
224
  @spec correlate_event_to_ast(pid() | atom(), map()) :: {:ok, ast_context()} | {:error, term()}
225
  def correlate_event_to_ast(repo, event) do
226
    GenServer.call(__MODULE__, {:correlate_event_to_ast, repo, event}, @correlation_timeout)
520✔
227
  end
228
  
229
  @doc """
230
  Gets comprehensive AST context for a runtime event.
231
  
232
  Provides detailed AST metadata including CFG/DFG context,
233
  variable scope, and call hierarchy.
234
  
235
  ## Parameters
236
  
237
  - `repo` - The Enhanced AST Repository
238
  - `event` - Runtime event
239
  
240
  ## Returns
241
  
242
  - `{:ok, context}` - Comprehensive AST context
243
  - `{:error, reason}` - Context lookup failed
244
  """
245
  @spec get_runtime_context(pid() | atom(), map()) :: {:ok, ast_context()} | {:error, term()}
246
  def get_runtime_context(repo, event) do
247
    GenServer.call(__MODULE__, {:get_runtime_context, repo, event}, @context_lookup_timeout)
2✔
248
  end
249
  
250
  @doc """
251
  Enhances a runtime event with AST metadata.
252
  
253
  Enriches runtime events with structural information,
254
  data flow context, and AST-based insights.
255
  
256
  ## Parameters
257
  
258
  - `repo` - The Enhanced AST Repository
259
  - `event` - Runtime event to enhance
260
  
261
  ## Returns
262
  
263
  - `{:ok, enhanced_event}` - Event with AST metadata
264
  - `{:error, reason}` - Enhancement failed
265
  """
266
  @spec enhance_event_with_ast(pid() | atom(), map()) :: {:ok, enhanced_event()} | {:error, term()}
267
  def enhance_event_with_ast(repo, event) do
268
    GenServer.call(__MODULE__, {:enhance_event_with_ast, repo, event}, @correlation_timeout)
2✔
269
  end
270
  
271
  @doc """
272
  Builds AST-aware execution traces from runtime events.
273
  
274
  Creates comprehensive execution traces that show both
275
  runtime behavior and underlying AST structure.
276
  
277
  ## Parameters
278
  
279
  - `repo` - The Enhanced AST Repository
280
  - `events` - List of runtime events
281
  
282
  ## Returns
283
  
284
  - `{:ok, trace}` - AST-aware execution trace
285
  - `{:error, reason}` - Trace building failed
286
  """
287
  @spec build_execution_trace(pid() | atom(), list(map())) :: {:ok, execution_trace()} | {:error, term()}
288
  def build_execution_trace(repo, events) do
289
    GenServer.call(__MODULE__, {:build_execution_trace, repo, events}, @query_enhancement_timeout)
3✔
290
  end
291
  
292
  @doc """
293
  Sets a structural breakpoint based on AST patterns.
294
  
295
  Enables breaking on specific AST patterns during execution,
296
  such as pattern match failures or specific call structures.
297
  
298
  ## Parameters
299
  
300
  - `breakpoint_spec` - Structural breakpoint specification
301
  
302
  ## Examples
303
  
304
      # Break on any pattern match failure in GenServer handle_call
305
      RuntimeCorrelator.set_structural_breakpoint(%{
306
        pattern: quote(do: {:handle_call, _, _}),
307
        condition: :pattern_match_failure,
308
        ast_path: ["MyGenServer", "handle_call"]
309
      })
310
  """
311
  @spec set_structural_breakpoint(map()) :: {:ok, String.t()} | {:error, term()}
312
  def set_structural_breakpoint(breakpoint_spec) do
313
    GenServer.call(__MODULE__, {:set_structural_breakpoint, breakpoint_spec})
103✔
314
  end
315
  
316
  @doc """
317
  Sets a data flow breakpoint for variable tracking.
318
  
319
  Enables breaking when variables flow through specific AST paths,
320
  providing deep insight into data movement through code structure.
321
  
322
  ## Parameters
323
  
324
  - `breakpoint_spec` - Data flow breakpoint specification
325
  
326
  ## Examples
327
  
328
      # Break when user_id flows through authentication path
329
      RuntimeCorrelator.set_data_flow_breakpoint(%{
330
        variable: "user_id",
331
        ast_path: ["MyModule", "authenticate", "case_clause_2"],
332
        flow_conditions: [:assignment, :pattern_match]
333
      })
334
  """
335
  @spec set_data_flow_breakpoint(map()) :: {:ok, String.t()} | {:error, term()}
336
  def set_data_flow_breakpoint(breakpoint_spec) do
337
    GenServer.call(__MODULE__, {:set_data_flow_breakpoint, breakpoint_spec})
2✔
338
  end
339
  
340
  @doc """
341
  Sets a semantic watchpoint for variable tracking.
342
  
343
  Tracks variables through AST structure rather than just scope,
344
  providing semantic understanding of variable flow.
345
  
346
  ## Parameters
347
  
348
  - `watchpoint_spec` - Semantic watchpoint specification
349
  
350
  ## Examples
351
  
352
      # Watch state variable through AST structure
353
      RuntimeCorrelator.set_semantic_watchpoint(%{
354
        variable: "state",
355
        track_through: [:pattern_match, :pipe_operator, :function_call],
356
        ast_scope: "MyGenServer.handle_call/3"
357
      })
358
  """
359
  @spec set_semantic_watchpoint(map()) :: {:ok, String.t()} | {:error, term()}
360
  def set_semantic_watchpoint(watchpoint_spec) do
361
    GenServer.call(__MODULE__, {:set_semantic_watchpoint, watchpoint_spec})
102✔
362
  end
363
  
364
  @doc """
365
  Gets correlation statistics and performance metrics.
366
  """
367
  @spec get_correlation_stats() :: {:ok, map()}
368
  def get_correlation_stats() do
369
    GenServer.call(__MODULE__, :get_correlation_stats)
4✔
370
  end
371
  
372
  @doc """
373
  Clears correlation caches and resets statistics.
374
  """
375
  @spec clear_caches() :: :ok
376
  def clear_caches() do
377
    GenServer.call(__MODULE__, :clear_caches)
1✔
378
  end
379
  
380
  # GenServer Callbacks
381
  
382
  def handle_call({:correlate_event_to_ast, repo, event}, _from, state) do
383
    start_time = System.monotonic_time(:millisecond)
520✔
384
    
385
    case correlate_event_internal(repo, event, state) do
520✔
386
      {:ok, ast_context} ->
387
        end_time = System.monotonic_time(:millisecond)
506✔
388
        duration = end_time - start_time
506✔
389
        
390
        # Update statistics
391
        new_stats = update_correlation_stats(state.correlation_stats, :correlation, duration)
506✔
392
        new_state = %{state | correlation_stats: new_stats}
506✔
393
        
394
        {:reply, {:ok, ast_context}, new_state}
506✔
395
      
396
      error ->
397
        {:reply, error, state}
14✔
398
    end
399
  end
400
  
401
  def handle_call({:get_runtime_context, repo, event}, _from, state) do
402
    start_time = System.monotonic_time(:millisecond)
2✔
403
    
404
    case get_runtime_context_internal(repo, event, state) do
2✔
405
      {:ok, context} ->
406
        end_time = System.monotonic_time(:millisecond)
2✔
407
        duration = end_time - start_time
2✔
408
        
409
        # Update statistics
410
        new_stats = update_correlation_stats(state.correlation_stats, :context_lookup, duration)
2✔
411
        new_state = %{state | correlation_stats: new_stats}
2✔
412
        
413
        {:reply, {:ok, context}, new_state}
2✔
414
      
415
      error ->
416
        {:reply, error, state}
×
417
    end
418
  end
419
  
420
  def handle_call({:enhance_event_with_ast, repo, event}, _from, state) do
421
    case enhance_event_internal(repo, event, state) do
2✔
422
      {:ok, enhanced_event} ->
423
        {:reply, {:ok, enhanced_event}, state}
1✔
424
      
425
      error ->
426
        {:reply, error, state}
1✔
427
    end
428
  end
429
  
430
  def handle_call({:build_execution_trace, repo, events}, _from, state) do
431
    case build_execution_trace_internal(repo, events, state) do
3✔
432
      {:ok, trace} ->
433
        {:reply, {:ok, trace}, state}
3✔
434
      
435
      error ->
436
        {:reply, error, state}
×
437
    end
438
  end
439
  
440
  def handle_call({:set_structural_breakpoint, breakpoint_spec}, _from, state) do
441
    case create_structural_breakpoint(breakpoint_spec) do
103✔
442
      {:ok, breakpoint_id, breakpoint} ->
443
        new_structural = Map.put(state.breakpoints.structural, breakpoint_id, breakpoint)
102✔
444
        new_breakpoints = %{state.breakpoints | structural: new_structural}
102✔
445
        new_state = %{state | breakpoints: new_breakpoints}
102✔
446
        
447
        {:reply, {:ok, breakpoint_id}, new_state}
102✔
448
      
449
      error ->
450
        {:reply, error, state}
1✔
451
    end
452
  end
453
  
454
  def handle_call({:set_data_flow_breakpoint, breakpoint_spec}, _from, state) do
455
    case create_data_flow_breakpoint(breakpoint_spec) do
2✔
456
      {:ok, breakpoint_id, breakpoint} ->
457
        new_data_flow = Map.put(state.breakpoints.data_flow, breakpoint_id, breakpoint)
1✔
458
        new_breakpoints = %{state.breakpoints | data_flow: new_data_flow}
1✔
459
        new_state = %{state | breakpoints: new_breakpoints}
1✔
460
        
461
        {:reply, {:ok, breakpoint_id}, new_state}
1✔
462
      
463
      error ->
464
        {:reply, error, state}
1✔
465
    end
466
  end
467
  
468
  def handle_call({:set_semantic_watchpoint, watchpoint_spec}, _from, state) do
469
    case create_semantic_watchpoint(watchpoint_spec) do
102✔
470
      {:ok, watchpoint_id, watchpoint} ->
471
        new_watchpoints = Map.put(state.watchpoints, watchpoint_id, watchpoint)
101✔
472
        new_state = %{state | watchpoints: new_watchpoints}
101✔
473
        
474
        {:reply, {:ok, watchpoint_id}, new_state}
101✔
475
      
476
      error ->
477
        {:reply, error, state}
1✔
478
    end
479
  end
480
  
481
  def handle_call(:get_correlation_stats, _from, state) do
482
    stats = %{
4✔
483
      correlation: state.correlation_stats,
4✔
484
      cache: state.cache_stats,
4✔
485
      breakpoints: %{
486
        structural: map_size(state.breakpoints.structural),
4✔
487
        data_flow: map_size(state.breakpoints.data_flow)
4✔
488
      },
489
      watchpoints: map_size(state.watchpoints)
4✔
490
    }
491
    
492
    {:reply, {:ok, stats}, state}
4✔
493
  end
494
  
495
  def handle_call(:clear_caches, _from, state) do
496
    :ets.delete_all_objects(@context_cache)
1✔
497
    :ets.delete_all_objects(@trace_cache)
1✔
498
    
499
    new_cache_stats = %{
1✔
500
      context_cache_size: 0,
501
      trace_cache_size: 0,
502
      evictions: state.cache_stats.evictions
1✔
503
    }
504
    
505
    new_state = %{state | cache_stats: new_cache_stats}
1✔
506
    {:reply, :ok, new_state}
1✔
507
  end
508
  
509
  # Private Implementation
510
  
511
  defp correlate_event_internal(_repo, nil, _state) do
2✔
512
    {:error, :nil_event}
513
  end
514
  
515
  defp correlate_event_internal(_repo, event, _state) when not is_map(event) do
×
516
    {:error, :invalid_event_type}
517
  end
518
  
519
  defp correlate_event_internal(repo, event, state) do
520
    # Validate required fields
521
    module = extract_module(event)
530✔
522
    function = extract_function(event)
530✔
523
    
524
    cond do
530✔
525
      is_nil(module) -> {:error, :missing_module}
6✔
526
      is_nil(function) -> {:error, :missing_function}
524✔
527
      not is_atom(module) -> {:error, :invalid_module_type}
524✔
528
      not is_atom(function) -> {:error, :invalid_function_type}
522✔
529
      true -> correlate_event_internal_validated(repo, event, state)
522✔
530
    end
531
  end
532
  
533
  defp correlate_event_internal_validated(repo, event, state) do
534
    # Check cache first
535
    cache_key = generate_correlation_cache_key(event)
522✔
536
    
537
    case :ets.lookup(@context_cache, cache_key) do
522✔
538
      [{^cache_key, {ast_context, timestamp}}] ->
539
        if System.monotonic_time(:millisecond) - timestamp < @cache_ttl do
498✔
540
          # Cache hit
541
          _new_stats = update_correlation_stats(state.correlation_stats, :cache_hit, 0)
498✔
542
          {:ok, ast_context}
543
        else
544
          # Cache expired
545
          :ets.delete(@context_cache, cache_key)
×
546
          correlate_event_fresh(repo, event, cache_key, state)
×
547
        end
548
      
549
      [] ->
550
        # Cache miss
551
        correlate_event_fresh(repo, event, cache_key, state)
24✔
552
    end
553
  end
554
  
555
  defp correlate_event_fresh(repo, event, cache_key, state) do
556
    with {:ok, module_data} <- get_module_data(repo, extract_module(event)),
24✔
557
         {:ok, function_data} <- get_function_data(module_data, extract_function(event), extract_arity(event)),
21✔
558
         {:ok, ast_node_id} <- generate_ast_node_id(event, function_data),
16✔
559
         {:ok, ast_context} <- build_ast_context(function_data, ast_node_id, event) do
16✔
560
      
561
      # Cache the result
562
      timestamp = System.monotonic_time(:millisecond)
16✔
563
      :ets.insert(@context_cache, {cache_key, {ast_context, timestamp}})
16✔
564
      
565
      # Update cache statistics
566
      _new_stats = update_correlation_stats(state.correlation_stats, :cache_miss, 0)
16✔
567
      
568
      {:ok, ast_context}
569
    else
570
      error -> error
8✔
571
    end
572
  end
573
  
574
  defp get_runtime_context_internal(repo, event, state) do
575
    with {:ok, ast_context} <- correlate_event_internal(repo, event, state),
2✔
576
         {:ok, cfg_context} <- get_cfg_context(repo, ast_context),
2✔
577
         {:ok, dfg_context} <- get_dfg_context(repo, ast_context),
2✔
578
         {:ok, variable_scope} <- get_variable_scope(repo, ast_context, event),
2✔
579
         {:ok, call_context} <- get_call_context(repo, event) do
2✔
580
      
581
      enhanced_context = %{
2✔
582
        ast_context |
583
        cfg_node: cfg_context,
584
        dfg_context: dfg_context,
585
        variable_scope: variable_scope,
586
        call_context: call_context
587
      }
588
      
589
      {:ok, enhanced_context}
590
    else
591
      error -> error
×
592
    end
593
  end
594
  
595
  defp enhance_event_internal(repo, event, state) do
596
    with {:ok, ast_context} <- correlate_event_internal(repo, event, state),
10✔
597
         {:ok, structural_info} <- extract_structural_info(ast_context),
6✔
598
         {:ok, data_flow_info} <- extract_data_flow_info(repo, ast_context, event) do
6✔
599
      
600
      enhanced_event = %{
6✔
601
        original_event: event,
602
        ast_context: ast_context,
603
        correlation_metadata: %{
604
          correlation_time: System.monotonic_time(:nanosecond),
605
          correlation_version: "1.0"
606
        },
607
        structural_info: structural_info,
608
        data_flow_info: data_flow_info
609
      }
610
      
611
      {:ok, enhanced_event}
612
    else
613
      error -> error
4✔
614
    end
615
  end
616
  
617
  defp build_execution_trace_internal(repo, events, _state) do
618
    with {:ok, enhanced_events} <- enhance_events_batch(repo, events),
3✔
619
         {:ok, ast_flow} <- build_ast_flow(enhanced_events),
3✔
620
         {:ok, variable_flow} <- build_variable_flow(enhanced_events),
3✔
621
         {:ok, structural_patterns} <- identify_structural_patterns(enhanced_events),
3✔
622
         {:ok, performance_correlation} <- correlate_performance_data(enhanced_events) do
3✔
623
      
624
      trace = %{
3✔
625
        events: enhanced_events,
626
        ast_flow: ast_flow,
627
        variable_flow: variable_flow,
628
        structural_patterns: structural_patterns,
629
        performance_correlation: performance_correlation,
630
        trace_metadata: %{
631
          trace_id: generate_trace_id(),
632
          created_at: System.system_time(:nanosecond),
633
          event_count: length(events),
634
          correlation_version: "1.0"
635
        }
636
      }
637
      
638
      {:ok, trace}
639
    else
640
      error -> error
×
641
    end
642
  end
643
  
644
  defp create_structural_breakpoint(spec) do
645
    breakpoint_id = generate_breakpoint_id("structural")
103✔
646
    
647
    breakpoint = %{
103✔
648
      id: breakpoint_id,
649
      pattern: Map.get(spec, :pattern),
650
      condition: Map.get(spec, :condition, :any),
651
      ast_path: Map.get(spec, :ast_path, []),
652
      enabled: Map.get(spec, :enabled, true),
653
      hit_count: 0,
654
      metadata: Map.get(spec, :metadata, %{})
655
    }
656
    
657
    case validate_structural_breakpoint(breakpoint) do
103✔
658
      :ok -> {:ok, breakpoint_id, breakpoint}
102✔
659
      error -> error
1✔
660
    end
661
  end
662
  
663
  defp create_data_flow_breakpoint(spec) do
664
    breakpoint_id = generate_breakpoint_id("data_flow")
2✔
665
    
666
    breakpoint = %{
2✔
667
      id: breakpoint_id,
668
      variable: Map.get(spec, :variable),
669
      ast_path: Map.get(spec, :ast_path, []),
670
      flow_conditions: Map.get(spec, :flow_conditions, [:any]),
671
      enabled: Map.get(spec, :enabled, true),
672
      hit_count: 0,
673
      metadata: Map.get(spec, :metadata, %{})
674
    }
675
    
676
    case validate_data_flow_breakpoint(breakpoint) do
2✔
677
      :ok -> {:ok, breakpoint_id, breakpoint}
1✔
678
      error -> error
1✔
679
    end
680
  end
681
  
682
  defp create_semantic_watchpoint(spec) do
683
    watchpoint_id = generate_watchpoint_id()
102✔
684
    
685
    watchpoint = %{
102✔
686
      id: watchpoint_id,
687
      variable: Map.get(spec, :variable),
688
      track_through: Map.get(spec, :track_through, [:all]),
689
      ast_scope: Map.get(spec, :ast_scope),
690
      enabled: Map.get(spec, :enabled, true),
691
      value_history: [],
692
      metadata: Map.get(spec, :metadata, %{})
693
    }
694
    
695
    case validate_semantic_watchpoint(watchpoint) do
102✔
696
      :ok -> {:ok, watchpoint_id, watchpoint}
101✔
697
      error -> error
1✔
698
    end
699
  end
700
  
701
  # Helper Functions
702
  
703
  defp extract_module(%{module: module}), do: module
563✔
704
  defp extract_module(%{"module" => module}), do: module
3✔
705
  defp extract_module(_), do: nil
4✔
706
  
707
  defp extract_function(%{function: function}), do: function
560✔
708
  defp extract_function(%{"function" => function}), do: function
3✔
709
  defp extract_function(_), do: nil
4✔
710
  
711
  defp extract_arity(%{arity: arity}), do: arity
32✔
712
  defp extract_arity(%{"arity" => arity}), do: arity
2✔
713
  defp extract_arity(_), do: 0
3✔
714
  
715
  defp extract_timestamp(%{timestamp: timestamp}), do: timestamp
18✔
716
  defp extract_timestamp(%{"timestamp" => timestamp}), do: timestamp
×
717
  defp extract_timestamp(_), do: System.monotonic_time(:nanosecond)
×
718
  
719
  defp get_module_data(repo, module) when not is_nil(module) do
720
    case EnhancedRepository.get_enhanced_module(module) do
24✔
721
      {:ok, module_data} -> {:ok, module_data}
21✔
722
      {:error, :not_found} -> {:error, :module_not_found}
3✔
723
      error -> error
×
724
    end
725
  end
726
  
727
  defp get_module_data(_repo, nil), do: {:error, :invalid_module}
×
728
  
729
  defp get_function_data(module_data, function, arity) when not is_nil(function) do
730
    case Map.get(module_data.functions, {function, arity}) do
21✔
731
      nil -> {:error, :function_not_found}
5✔
732
      function_data -> {:ok, function_data}
16✔
733
    end
734
  end
735
  
736
  defp get_function_data(_module_data, nil, _arity), do: {:error, :invalid_function}
×
737
  
738
  defp generate_ast_node_id(event, function_data) do
739
    # Check if event already has ast_node_id
740
    case Map.get(event, :ast_node_id) do
16✔
741
      nil ->
742
        # Generate ast_node_id from event data
743
        module = extract_module(event)
16✔
744
        function = extract_function(event)
16✔
745
        arity = extract_arity(event)
16✔
746
        line = extract_line_number(event, function_data)
16✔
747
        
748
        # Use short module name (last part after the last dot)
749
        short_module_name = case to_string(module) do
16✔
750
          "Elixir." <> rest -> 
751
            rest |> String.split(".") |> List.last()
16✔
752
          module_str -> 
753
            module_str |> String.split(".") |> List.last()
×
754
        end
755
        
756
        node_id = "#{short_module_name}.#{function}/#{arity}:line_#{line}"
16✔
757
        {:ok, node_id}
758
      
759
      existing_ast_node_id ->
×
760
        # Use existing ast_node_id from event
761
        {:ok, existing_ast_node_id}
762
    end
763
  end
764
  
765
  defp extract_line_number(event, function_data) do
766
    # Try to extract line number from event or use function start line
767
    cond do
32✔
768
      Map.has_key?(event, :caller_line) and not is_nil(Map.get(event, :caller_line)) ->
32✔
769
        Map.get(event, :caller_line)
×
770
      Map.has_key?(event, :line) and not is_nil(Map.get(event, :line)) ->
32✔
771
        Map.get(event, :line)
×
772
      true ->
32✔
773
        function_data.line_start
32✔
774
    end
775
  end
776
  
777
  defp build_ast_context(function_data, ast_node_id, event) do
778
    context = %{
16✔
779
      module: function_data.module_name,
16✔
780
      function: function_data.function_name,
16✔
781
      arity: function_data.arity,
16✔
782
      ast_node_id: ast_node_id,
783
      line_number: extract_line_number(event, function_data),
784
      ast_metadata: %{
785
        complexity: function_data.complexity,
16✔
786
        visibility: function_data.visibility,
16✔
787
        file_path: function_data.file_path,
16✔
788
        line_start: function_data.line_start,
16✔
789
        line_end: function_data.line_end
16✔
790
      },
791
      cfg_node: nil,
792
      dfg_context: nil,
793
      variable_scope: %{},
794
      call_context: []
795
    }
796
    
797
    {:ok, context}
798
  end
799
  
800
  defp get_cfg_context(repo, ast_context) do
801
    # Get CFG data for the function
802
    case EnhancedRepository.get_enhanced_function(ast_context.module, ast_context.function, ast_context.arity) do
2✔
803
      {:ok, function_data} ->
804
        # Find the CFG node for the current line
805
        cfg_node = find_cfg_node_for_line(function_data.cfg_data, ast_context.line_number)
×
806
        {:ok, cfg_node}
807
      
808
      {:error, :not_found} ->
2✔
809
        {:ok, nil}
810
      
811
      error ->
812
        error
×
813
    end
814
  end
815
  
816
  defp get_dfg_context(repo, ast_context) do
817
    # Get DFG data for the function
818
    case EnhancedRepository.get_enhanced_function(ast_context.module, ast_context.function, ast_context.arity) do
8✔
819
      {:ok, function_data} ->
820
        # Extract relevant DFG context for the current line
821
        dfg_context = extract_dfg_context_for_line(function_data.dfg_data, ast_context.line_number)
×
822
        {:ok, dfg_context}
823
      
824
      {:error, :not_found} ->
8✔
825
        {:ok, nil}
826
      
827
      error ->
828
        error
×
829
    end
830
  end
831
  
832
  defp get_variable_scope(_repo, ast_context, event) do
833
    # Extract variable scope from event if available
834
    variables = case Map.get(event, :variables) do
2✔
835
      nil -> %{}
2✔
836
      vars when is_map(vars) -> vars
×
837
      _ -> %{}
×
838
    end
839
    
840
    scope = %{
2✔
841
      local_variables: variables,
842
      scope_level: ast_context.line_number,
2✔
843
      binding_context: extract_binding_context(event)
844
    }
845
    
846
    {:ok, scope}
847
  end
848
  
849
  defp get_call_context(_repo, event) do
850
    # Extract call context from correlation ID and call stack
851
    call_context = case Map.get(event, :correlation_id) do
2✔
852
      nil -> []
×
853
      correlation_id -> build_call_context_from_correlation(correlation_id)
2✔
854
    end
855
    
856
    {:ok, call_context}
857
  end
858
  
859
  defp extract_structural_info(ast_context) do
860
    structural_info = %{
6✔
861
      ast_node_type: determine_ast_node_type(ast_context),
862
      structural_depth: calculate_structural_depth(ast_context),
863
      pattern_context: extract_pattern_context(ast_context),
864
      control_flow_position: determine_control_flow_position(ast_context)
865
    }
866
    
867
    {:ok, structural_info}
868
  end
869
  
870
  defp extract_data_flow_info(repo, ast_context, event) do
871
    with {:ok, dfg_context} <- get_dfg_context(repo, ast_context) do
6✔
872
      data_flow_info = %{
6✔
873
        variable_definitions: extract_variable_definitions(dfg_context),
874
        variable_uses: extract_variable_uses(dfg_context),
875
        data_dependencies: extract_data_dependencies(dfg_context),
876
        flow_direction: determine_flow_direction(event, dfg_context)
877
      }
878
      
879
      {:ok, data_flow_info}
880
    else
881
      error -> error
×
882
    end
883
  end
884
  
885
  defp enhance_events_batch(repo, events) do
886
    # Create a minimal state structure for internal calls
887
    minimal_state = %{
3✔
888
      correlation_stats: %{
889
        events_correlated: 0,
890
        context_lookups: 0,
891
        cache_hits: 0,
892
        cache_misses: 0
893
      }
894
    }
895
    
896
    enhanced_events = Enum.map(events, fn event ->
3✔
897
      case enhance_event_internal(repo, event, minimal_state) do
8✔
898
        {:ok, enhanced_event} -> enhanced_event
5✔
899
        {:error, _} -> 
900
          # Fallback to original event if enhancement fails
901
          %{
3✔
902
            original_event: event,
903
            ast_context: nil,
904
            correlation_metadata: %{},
905
            structural_info: %{},
906
            data_flow_info: %{}
907
          }
908
      end
909
    end)
910
    
911
    {:ok, enhanced_events}
912
  end
913
  
914
  defp build_ast_flow(enhanced_events) do
915
    ast_flow = enhanced_events
3✔
916
    |> Enum.filter(fn event -> not is_nil(event.ast_context) end)
8✔
917
    |> Enum.map(fn event ->
918
      %{
5✔
919
        ast_node_id: event.ast_context.ast_node_id,
5✔
920
        timestamp: extract_timestamp(event.original_event),
5✔
921
        structural_info: event.structural_info
5✔
922
      }
923
    end)
924
    |> Enum.sort_by(& &1.timestamp)
5✔
925
    
926
    {:ok, ast_flow}
927
  end
928
  
929
  defp build_variable_flow(enhanced_events) do
930
    variable_flow = enhanced_events
3✔
931
    |> Enum.reduce(%{}, fn event, acc ->
932
      case event.ast_context do
8✔
933
        nil -> 
934
          # Handle events without ast_context but with variables in original event
935
          case Map.get(event.original_event, :variables) do
3✔
936
            vars when is_map(vars) and map_size(vars) > 0 ->
937
              # Create a synthetic ast_node_id for variable tracking
938
              module = Map.get(event.original_event, :module, "Unknown")
3✔
939
              function = Map.get(event.original_event, :function, "unknown")
3✔
940
              synthetic_ast_node_id = "#{module}.#{function}:variable_snapshot"
3✔
941
              
942
              Enum.reduce(vars, acc, fn {var_name, var_value}, var_acc ->
3✔
943
                var_history = Map.get(var_acc, var_name, [])
7✔
944
                var_entry = %{
7✔
945
                  value: var_value,
946
                  timestamp: extract_timestamp(event.original_event),
7✔
947
                  ast_node_id: synthetic_ast_node_id,
948
                  line_number: Map.get(event.original_event, :line, 0)
7✔
949
                }
950
                Map.put(var_acc, var_name, [var_entry | var_history])
7✔
951
              end)
952
            _ -> acc
×
953
          end
954
        context ->
955
          # Safely get local variables from variable_scope
956
          local_variables = case Map.get(context, :variable_scope, %{}) do
5✔
957
            %{local_variables: vars} when is_map(vars) -> vars
×
958
            vars when is_map(vars) -> vars
5✔
959
            _ -> %{}
×
960
          end
961
          
962
          Enum.reduce(local_variables, acc, fn {var_name, var_value}, var_acc ->
5✔
963
            var_history = Map.get(var_acc, var_name, [])
×
964
            var_entry = %{
×
965
              value: var_value,
966
              timestamp: extract_timestamp(event.original_event),
×
967
              ast_node_id: context.ast_node_id,
×
968
              line_number: context.line_number
×
969
            }
970
            Map.put(var_acc, var_name, [var_entry | var_history])
×
971
          end)
972
      end
973
    end)
974
    |> Enum.map(fn {var_name, history} ->
3✔
975
      {var_name, Enum.reverse(history)}
976
    end)
977
    |> Enum.into(%{})
978
    
979
    {:ok, variable_flow}
980
  end
981
  
982
  defp identify_structural_patterns(enhanced_events) do
983
    patterns = enhanced_events
3✔
984
    |> Enum.filter(fn event -> 
985
      not is_nil(event.structural_info) and Map.has_key?(event.structural_info, :ast_node_type)
8✔
986
    end)
987
    |> Enum.group_by(fn event -> event.structural_info.ast_node_type end)
5✔
988
    |> Enum.map(fn {pattern_type, events} ->
989
      %{
2✔
990
        pattern_type: pattern_type,
991
        occurrences: length(events),
992
        first_occurrence: extract_timestamp(hd(events).original_event),
2✔
993
        last_occurrence: extract_timestamp(List.last(events).original_event)
2✔
994
      }
995
    end)
996
    
997
    {:ok, patterns}
998
  end
999
  
1000
  defp correlate_performance_data(enhanced_events) do
1001
    performance_data = enhanced_events
3✔
1002
    |> Enum.filter(fn event -> 
1003
      Map.has_key?(event.original_event, :duration_ns) and not is_nil(event.ast_context)
8✔
1004
    end)
1005
    |> Enum.map(fn event ->
1006
      %{
2✔
1007
        ast_node_id: event.ast_context.ast_node_id,
2✔
1008
        duration_ns: event.original_event.duration_ns,
2✔
1009
        complexity: event.ast_context.ast_metadata.complexity,
2✔
1010
        timestamp: extract_timestamp(event.original_event)
2✔
1011
      }
1012
    end)
1013
    |> Enum.group_by(& &1.ast_node_id)
2✔
1014
    |> Enum.map(fn {ast_node_id, measurements} ->
1015
      durations = Enum.map(measurements, & &1.duration_ns)
2✔
1016
      complexity = hd(measurements).complexity
2✔
1017
      
1018
      {ast_node_id, %{
1019
        avg_duration: Enum.sum(durations) / length(durations),
1020
        min_duration: Enum.min(durations),
1021
        max_duration: Enum.max(durations),
1022
        call_count: length(measurements),
1023
        complexity: complexity,
1024
        performance_ratio: calculate_performance_ratio(durations, complexity)
1025
      }}
1026
    end)
1027
    |> Enum.into(%{})
2✔
1028
    
1029
    {:ok, performance_data}
1030
  end
1031
  
1032
  # Utility Functions
1033
  
1034
  defp generate_correlation_cache_key(event) when is_nil(event) do
1035
    "nil_event_#{System.unique_integer()}"
×
1036
  end
1037
  
1038
  defp generate_correlation_cache_key(event) when is_map(event) do
1039
    module = Map.get(event, :module, "unknown")
522✔
1040
    function = Map.get(event, :function, "unknown")
522✔
1041
    arity = Map.get(event, :arity, 0)
522✔
1042
    line = Map.get(event, :line, 0)
522✔
1043
    
1044
    "#{module}.#{function}/#{arity}:#{line}"
522✔
1045
  end
1046
  
1047
  defp generate_correlation_cache_key(_event) do
1048
    "invalid_event_#{System.unique_integer()}"
×
1049
  end
1050
  
1051
  defp generate_trace_id() do
1052
    "trace_" <> Base.encode16(:crypto.strong_rand_bytes(8), case: :lower)
3✔
1053
  end
1054
  
1055
  defp generate_breakpoint_id(type) do
1056
    "#{type}_bp_" <> Base.encode16(:crypto.strong_rand_bytes(4), case: :lower)
105✔
1057
  end
1058
  
1059
  defp generate_watchpoint_id() do
1060
    "wp_" <> Base.encode16(:crypto.strong_rand_bytes(4), case: :lower)
102✔
1061
  end
1062
  
1063
  defp update_correlation_stats(stats, operation, duration) do
1064
    case operation do
1,022✔
1065
      :correlation ->
1066
        %{stats | events_correlated: stats.events_correlated + 1}
506✔
1067
      
1068
      :context_lookup ->
1069
        %{stats | context_lookups: stats.context_lookups + 1}
2✔
1070
      
1071
      :cache_hit ->
1072
        %{stats | cache_hits: stats.cache_hits + 1}
498✔
1073
      
1074
      :cache_miss ->
1075
        %{stats | cache_misses: stats.cache_misses + 1}
16✔
1076
    end
1077
  end
1078
  
1079
  # Placeholder implementations for complex analysis functions
1080
  
1081
  defp find_cfg_node_for_line(_cfg_data, _line_number), do: nil
×
1082
  defp extract_dfg_context_for_line(_dfg_data, _line_number), do: %{}
×
1083
  defp extract_binding_context(_event), do: %{}
2✔
1084
  defp build_call_context_from_correlation(_correlation_id), do: []
2✔
1085
  defp determine_ast_node_type(_ast_context), do: :function_call
6✔
1086
  defp calculate_structural_depth(_ast_context), do: 1
6✔
1087
  defp extract_pattern_context(_ast_context), do: %{}
6✔
1088
  defp determine_control_flow_position(_ast_context), do: :sequential
6✔
1089
  defp extract_variable_definitions(_dfg_context), do: []
6✔
1090
  defp extract_variable_uses(_dfg_context), do: []
6✔
1091
  defp extract_data_dependencies(_dfg_context), do: []
6✔
1092
  defp determine_flow_direction(_event, _dfg_context), do: :forward
6✔
1093
  defp calculate_performance_ratio(durations, complexity) do
1094
    avg_duration = Enum.sum(durations) / length(durations)
2✔
1095
    avg_duration / max(complexity, 1)
2✔
1096
  end
1097
  
1098
  defp validate_structural_breakpoint(%{pattern: pattern}) when not is_nil(pattern), do: :ok
102✔
1099
  defp validate_structural_breakpoint(_), do: {:error, :invalid_pattern}
1✔
1100
  
1101
  defp validate_data_flow_breakpoint(%{variable: variable}) when not is_nil(variable), do: :ok
1✔
1102
  defp validate_data_flow_breakpoint(_), do: {:error, :invalid_variable}
1✔
1103
  
1104
  defp validate_semantic_watchpoint(%{variable: variable}) when not is_nil(variable), do: :ok
101✔
1105
  defp validate_semantic_watchpoint(_), do: {:error, :invalid_variable}
1✔
1106
end 
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc