llvm / llvm/llvm-project

[lldb] SBError::GetError() returns a placeholder for every expression error, so eExpressionParseError is unreachable

Open
#214,642 1 comment 0 reactions 1 assignee Claimed by @adrian-prantl View on GitHub
lldb
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

## Summary

For any failed expression evaluation, `SBError::GetError()` returns **3** regardless of what actually went wrong.

3 is not `eExpressionDiscarded` — it is `llvm::inconvertibleErrorCode().value()` (`ErrorErrorCode::InconvertibleError`), which happens to have the same numeric value. `SBError::GetType()` is still correct (`eErrorTypeExpression`), so an API client sees a well-formed pair and has no way to tell that the code is a placeholder.

`lldb::eExpressionParseError` is therefore unreachable through the public API, and a UI cannot distinguish a compile diagnostic from a runtime failure. `SBError::GetErrorData()` does not recover it either, so there is no supported fallback.

The practical effect is that unrelated failures became indistinguishable. Four different things that go wrong — a compile diagnostic, an interruption mid-call, running into a breakpoint, and a crash — used to report four different codes and now all report 3:

| Failure | 19.1.7 | 20.1.8 | 21.1.7 |
| --- | --- | --- | --- |
| parse error | **2** `eExpressionParseError` | 3 | 3 |
| interrupted mid-call (SIGSTOP) | **4** `eExpressionInterrupted` | 3 | 3 |
| hit a breakpoint during the call | **5** `eExpressionHitBreakpoint` | 3 | 3 |
| crashed inside the expression | **3** `eExpressionDiscarded` | 3 | 3 |

Only the last row is right, and only by coincidence. `repro_error_kind_collapse.py` produces this table; full output is attached.

Note that the human-readable message still distinguishes these cases perfectly (`"use of undeclared identifier"`, `"Expression execution was interrupted: signal SIGSTOP"`, `"Expression execution hit a breakpoint"`), so the information exists — it is only the machine-readable code that is gone. Clients are pushed towards parsing the message string.

## Reproducer

No debug info, no process and no special target — any executable will do.

```python
import lldb

lldb.SBDebugger.Initialize()
dbg = lldb.SBDebugger.Create()
dbg.SetAsync(False)
target = dbg.CreateTarget("/bin/ls") # any binary

for expr in ["1 + no_such_ident", "1 +", "sizeof(no_such_type_xyz)"]:
err = target.EvaluateExpression(expr).GetError()
print(f"{expr:<28} type={err.GetType()} code={err.GetError()}")
```

Two self-contained scripts are attached. Both locate the bindings via `lldb -P` and exit non-zero when affected:

- `repro_expression_error_code.py` — the snippet above plus a `GetErrorData()` dump. Needs no process.
- `repro_error_kind_collapse.py` — produces the four-way table above. Launches `/bin/ls` and calls into libc, so on Linux it needs `lldb-server` next to `lldb` and permission to ptrace a child.

### Observed — LLVM 21.1.7, x86_64 Linux

```
enums : eExpressionParseError=2 eExpressionDiscarded=3 eErrorTypeExpression=4

expression type code
------------------------------------------
1 + no_such_ident 4 3
1 + 4 3
sizeof(no_such_type_xyz) 4 3
```

### Expected

`code = 2` — the `lldb::ExpressionResults` the evaluator passed to `DiagnosticManager::GetAsError()`.

That is exactly what LLVM 19.1.7 returns, same script, same machine:

```
1 + no_such_ident 4 2
1 + 4 2
sizeof(no_such_type_xyz) 4 2
```

## Affected versions

| Version | Result | Evidence |
| --- | --- | --- |
| 19.1.7 | **not affected**, `code=2` | ran the reproducer (Linux x64 + Windows x64) |
| 20.1.8 | **affected**, `code=3` | ran the reproducer (Linux x64) |
| 21.1.7 | **affected**, `code=3` | ran the reproducer (Linux x64 + Windows x64) |
| 22.1.8 | affected | source identical at `DiagnosticManager.cpp:65-67` |

Checked 2026-08-07; the override is byte-identical in all of them. The official `LLVM--Linux-X64.tar.xz` release binaries were used for the runs.

This is a regression from `84fdfb9ca63ee4304b486d7e85545ee4e1a46f5d` / #106442, which is labelled **NFC**. That commit introduced `ExpressionError` in its current form: before it, `ExpressionError` lived in `lldb/include/lldb/Utility/Status.h`, derived from `CloneableECError` and had **no** `convertToErrorCode()` override, so it inherited `CloneableECError::convertToErrorCode()`, which returns `EC`, and the kind came through. The commit landed 2024-09-27, i.e. in the LLVM 20 cycle — **the first affected release is 20.1.0, not 21.x.**

@adrian-prantl — you authored #106442, so tagging you rather than guessing at the right reviewer.

Label: lldb

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.