[clangd]opening a valid file with a recursive var-template pattern crashes in Stmt::children
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
Version:
- clangd 23.0.0git
- commit: c75e1e62d11be965a4dca0d816a4d19a6684b043
- Ubuntu 24.04 x86_64
- ASAN-enabled clangd
This crash only happens on the ASAN version of commit:`c75e1e62d11be965a4dca0d816a4d19a6684b043`. On the plain assertion build, I cannot reproduce.
Minimal source
```cxx
namespace InvalidInsertPos {
template T v;
template decltype(v) v;
template<> int v;
int k = v;
```
A very interesting aspect is that reproducibility depends on the depth in `v`.
Larger `N` values make the crash easier to reproduce on my machine.
In gdb on a plain/assert clangd build, I observed the later background path reaching
`clang::Sema::BuildReturnStmt` on the `IndexStdlib` thread, with a stack of the form:
clang::Sema::BuildReturnStmt
clang::Sema::ActOnReturnStmt
clang::Parser::ParseStatementOrDeclarationAfterAttributes
clang::Parser::ParseFunctionDefinition
On the ASAN build, the later failing path then crashes with a stack of the form:
clang::Stmt::children()
AnalyzeImplicitConversions
clang::Sema::CheckCompletedExpr
clang::Sema::ActOnFinishFullExpr
BuildConvertedConstantExpression
clang::Sema::CheckTemplateArgument
clang::Sema::CheckVarTemplateId
clang::Sema::BuildVarTemplateInstantiation
clang::TemplateDeclInstantiator::VisitVarTemplateSpecializationDecl
reproducer
```
python reproducer.py /path/to/asan/clangd
```
```python
#!/usr/bin/env python3
import json
import os
import shutil
import subprocess
import sys
import tempfile
import time
from pathlib import Path
from urllib.parse import quote
SOURCE = """namespace InvalidInsertPos {
template T v;
template decltype(v) v;
template<> int v;
int k = v;
}
"""
def file_uri(path: Path) -> str:
return "file://" + quote(str(path))
def encode_lsp(msg: dict) -> bytes:
body = json.dumps(msg, separators=(",", ":")).encode("utf-8")
return f"Content-Length: {len(body)}\r\n\r\n".encode("ascii") + body
def main() -> int:
if len(sys.argv) != 2:
print(f"usage: {sys.argv[0]} /path/to/asan-clangd", file=sys.stderr)
return 2
clangd = sys.argv[1]
workdir = Path(tempfile.mkdtemp(prefix="repro-id76-"))
source_path = workdir / "main.cpp"
source_path.write_text(SOURCE, encoding="utf-8")
uri = file_uri(source_path)
root_uri = file_uri(workdir)
messages = [
{
"jsonrpc": "2.0",
"id": 0,
"method": "initialize",
"params": {
"processId": None,
"rootUri": root_uri,
"capabilities": {},
"workspaceFolders": [{"uri": root_uri, "name": "workspace"}],
},
},
{"jsonrpc": "2.0", "method": "initialized", "params": {}},
{
"jsonrpc": "2.0",
"method": "textDocument/didOpen",
"params": {
"textDocument": {
"uri": uri,
"languageId": "cpp",
"version": 1,
"text": SOURCE,
}
},
},
]
env = os.environ.copy()
env["ASAN_OPTIONS"] = "detect_leaks=0"
env["LSAN_OPTIONS"] = "detect_leaks=0"
proc = subprocess.Popen(
[clangd, "--background-index=0", "--clang-tidy=0"],
stdin=subprocess.PIPE,
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
text=False,
env=env,
)
assert proc.stdin is not None
for msg in messages:
proc.stdin.write(encode_lsp(msg))
proc.stdin.flush()
time.sleep(0.05)
time.sleep(12.0)
try:
proc.stdin.close()
except Exception:
pass
if proc.poll() is None:
proc.kill()
stderr = proc.stderr.read().decode("utf-8", "replace") if proc.stderr else ""
rc = proc.wait(timeout=2)
reproduced = (
all(
marker in stderr
for marker in (
"clang::Stmt::children()",
"AnalyzeImplicitConversions",
"CheckCompletedExpr",
)
)
or (
"PLEASE submit a bug report" in stderr
and "Stack dump:" in stderr
and "current parser token" in stderr
)
)
print(
json.dumps(
{
"case": "id76",
"clangd": clangd,
"exit_code": rc,
"signal": -rc if rc < 0 else None,
"reproduced": reproduced,
"workdir": str(workdir),
"source_path": str(source_path),
},
ensure_ascii=False,
indent=2,
)
)
if stderr:
print("\n=== clangd stderr ===")
print(stderr, end="" if stderr.endswith("\n") else "\n")
shutil.rmtree(workdir, ignore_errors=True)
return 0 if reproduced else 1
if __name__ == "__main__":
raise SystemExit(main())
```
Contributor guide
Assessment
This issue has not been assessed yet.