Packages
metastatic
0.7.3
0.26.0
0.25.0
0.24.1
0.24.0
0.23.0
0.22.2
0.22.1
0.22.0
0.21.3
0.21.2
0.21.1
0.21.0
0.20.3
0.20.2
0.20.1
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.1
0.15.0
0.14.2
0.14.1
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.0
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.2
0.4.1
0.4.0
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
Cross-language code meta-model library using unified MetaAST representation. Parse, transform, and translate code across Python, Elixir, Ruby, Erlang, Haskell, and more via a shared three-tuple AST format.
Current section
Files
Jump to
Current section
Files
lib/metastatic/analysis/business_logic/missing_handle_async.ex
defmodule Metastatic.Analysis.BusinessLogic.MissingHandleAsync do
@moduledoc """
Detects missing async error handling (fire-and-forget without supervision).
Universal pattern: spawning async tasks without monitoring results or errors.
## Examples
**Python (asyncio without await):**
```python
async def process():
asyncio.create_task(heavy_work()) # Task created but never awaited - errors lost
return "done"
```
**JavaScript (Promise without catch):**
```javascript
function processData() {
fetchData(); // Returns Promise but not awaited/caught - errors swallowed
return success;
}
```
**Elixir (Task.start without monitoring):**
```elixir
def handle_cast(:process, state) do
Task.start(fn -> heavy_work() end) # Should use Task.Supervisor or Task.async + await
{:noreply, state}
end
```
**C# (Task.Run fire-and-forget):**
```csharp
void ProcessData() {
Task.Run(() => HeavyWork()); // Should await or use ContinueWith
return;
}
```
**Go (goroutine without sync):**
```go
func process() {
go heavyWork() // No WaitGroup or channel - completion/errors unknown
return
}
```
**Java (CompletableFuture without handling):**
```java
void processData() {
CompletableFuture.runAsync(() -> heavyWork()); // Should use get() or exceptionally()
return;
}
```
**Ruby (Thread without join):**
```ruby
def process
Thread.new { heavy_work } # Should join or use thread pool
return
end
```
"""
@behaviour Metastatic.Analysis.Analyzer
alias Metastatic.Analysis.Analyzer
@async_spawn_functions ~w[
create_task run_async start spawn
task async future thread goroutine
parallel background detached
]
@impl true
def info do
%{
name: :missing_handle_async,
category: :reliability,
description: "Detects unmonitored async operations",
severity: :warning,
explanation: "Async tasks should be supervised, awaited, or have error handlers",
configurable: true
}
end
@impl true
def analyze({:function_call, fn_name, args} = node, context)
when is_binary(fn_name) do
fn_lower = String.downcase(fn_name)
if String.contains?(fn_lower, @async_spawn_functions) and
not has_error_handling?(args, context) do
[
Analyzer.issue(
analyzer: __MODULE__,
category: :reliability,
severity: :warning,
message:
"Async operation '#{fn_name}' started without supervision/error handling - errors may be lost",
node: node,
metadata: %{
function: fn_name,
suggestion: "Use supervision, await result, or add error handling"
}
)
]
else
[]
end
end
def analyze(_node, _context), do: []
# Heuristic: check if there's error handling nearby (simplified)
defp has_error_handling?(_args, context) do
# Check if we're in a try/catch block or have supervision
Map.get(context, :in_exception_handling, false) or
Map.get(context, :supervised, false)
end
end