clang::Sema::InstantiateDefaultArgument crashes (null deref in addInstantiatedParametersToScope) when called from a Tooling client on a primary function template
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## Summary
Calling the public `clang::Sema::InstantiateDefaultArgument(SourceLocation, FunctionDecl*, ParmVarDecl*)` API on the `FunctionDecl` of a *primary* function template (rather than an instantiation) crashes inside `addInstantiatedParametersToScope` with a NULL pointer dereference. The function's only documented precondition is `assert(Param->hasUninstantiatedDefaultArg())`; nothing tells callers that `FD` must also be a template instantiation.
The same crash pattern affects `clang::Sema::InstantiateFunctionDefinition` — it null-derefs an invalid pattern decl when called on a primary template too.
## Reproducer
`repro.cpp` — minimal clang-tooling driver (no third-party deps):
```cpp
#include
#include
#include
#include
#include
#include
#include
using namespace clang;
namespace {
struct Visitor : RecursiveASTVisitor {
Sema &S;
explicit Visitor(Sema &S) : S(S) {}
bool shouldVisitTemplateInstantiations() const { return true; }
bool VisitFunctionDecl(FunctionDecl *D) {
for (ParmVarDecl *P : D->parameters())
if (P->hasUninstantiatedDefaultArg())
S.InstantiateDefaultArgument(D->getSourceRange().getBegin(), D, P);
return true;
}
};
struct Consumer : ASTConsumer {
CompilerInstance &CI;
explicit Consumer(CompilerInstance &CI) : CI(CI) {}
void HandleTranslationUnit(ASTContext &Ctx) override {
Visitor(CI.getSema()).TraverseDecl(Ctx.getTranslationUnitDecl());
}
};
struct Action : ASTFrontendAction {
std::unique_ptr CreateASTConsumer(CompilerInstance &CI, llvm::StringRef) override {
return std::make_unique(CI);
}
};
} // namespace
static llvm::cl::OptionCategory Cat("repro");
int main(int argc, const char **argv) {
auto OP = tooling::CommonOptionsParser::create(argc, argv, Cat);
if (!OP) { llvm::errs() << OP.takeError(); return 1; }
tooling::ClangTool Tool(OP->getCompilations(), OP->getSourcePathList());
return Tool.run(tooling::newFrontendActionFactory().get());
}
```
`repro_input.cpp` — the translation unit fed to the tool:
```cpp
template
struct Wrap {
template
void method(int x = sizeof(U)) {}
};
inline void use() {
Wrap w;
w.method();
}
```
Build and run:
```
clang++ -std=c++23 repro.cpp -o repro -lclang-cpp -lLLVM-NN
./repro repro_input.cpp -- -std=c++23
```
Note: plain `clang++ -fsyntax-only repro_input.cpp` does *not* crash — this is a Tooling-API issue only.
## Actual behavior
Segfault (`STATUS_ACCESS_VIOLATION` 0xC0000005 on Windows, SIGSEGV on Linux). Stack on clang 22.1.4 (MSYS2 mingw-w64-clang-x86_64):
```
#0 clang::FunctionDecl::getNumParams() const + 5
(this = invalid; reading offset 0x30 faults at address 0x30)
#1 clang::Sema::addInstantiatedParametersToScope(
FunctionDecl *NewFunction,
FunctionDecl const *PatternDecl, ← nullptr
LocalInstantiationScope &Scope,
MultiLevelTemplateArgumentList const &TemplateArgs)
clang/lib/Sema/SemaTemplateInstantiateDecl.cpp:5223
for (unsigned I = 0, N = PatternDecl->getNumParams(); I != N; ++I)
#2 clang::Sema::SubstDefaultArgument(
SourceLocation, ParmVarDecl*,
MultiLevelTemplateArgumentList const&, bool ForCallExpr)
clang/lib/Sema/SemaTemplateInstantiate.cpp:3204-3206
FunctionDecl *PatternFD = FD->getTemplateInstantiationPattern(
/*ForDefinition*/ false);
if (addInstantiatedParametersToScope(FD, PatternFD, *LIS, TemplateArgs))
#3 clang::Sema::InstantiateDefaultArgument(
SourceLocation, FunctionDecl*, ParmVarDecl*)
clang/lib/Sema/SemaTemplateInstantiateDecl.cpp:5318
if (SubstDefaultArgument(CallLoc, Param, TemplateArgs, /*ForCallExpr*/ true))
#4 user code (RecursiveASTVisitor::VisitFunctionDecl)
```
When the visited decl is the primary function template, `FD->getTemplateInstantiationPattern(/*ForDefinition*/ false)` returns `nullptr`. `addInstantiatedParametersToScope` then dereferences it without checking.
## Expected behavior
Any of:
1. Assert on the precondition (e.g. `assert(FD->isTemplateInstantiation())` at the top of `Sema::InstantiateDefaultArgument`) with a clear diagnostic.
2. Return gracefully (early-return `false`) when `getTemplateInstantiationPattern` is null.
3. Document the precondition in the API contract so callers know to check.
Crashing inside libclang on a public Sema entry point is the worst outcome — especially since the only documented precondition is `assert(Param->hasUninstantiatedDefaultArg())`, which is satisfied here.
## Related: same crash pattern in `Sema::InstantiateFunctionDefinition`
A tooling client that calls `Sema::InstantiateFunctionDefinition(SourceLocation, FunctionDecl*, ...)` on a primary template (rather than an instantiation) crashes with the same access-violation signature:
```
#0 clang::FunctionDecl::isDefined(FunctionDecl const*&, bool) + 40 ← null deref
#1 clang::Sema::InstantiateFunctionDefinition + 259
#2 user code
```
This API likely needs the same precondition fix.
## Discovered by
[mrbind](https://github.com/MeshInspector/mrbind), a Tooling-based bindings generator. It iterates `FunctionDecl::parameters()` and calls `Sema::InstantiateDefaultArgument` on any param with `hasUninstantiatedDefaultArg()`, processing both primary templates and their instantiations. The mrbind side was [fixed](https://github.com/MeshInspector/mrbind/pull/33) by skipping primary templates at the call sites — that's the obvious caller-side workaround, but the LLVM-side hardening would prevent the next Tooling client from hitting the same trap.
## Suggested fix
In `clang/lib/Sema/SemaTemplateInstantiate.cpp`, around the existing line 3204:
```cpp
if (ForCallExpr) {
LIS.emplace(*this);
FunctionDecl *PatternFD = FD->getTemplateInstantiationPattern(
/*ForDefinition*/ false);
if (!PatternFD) // <-- add null check
return true;
if (addInstantiatedParametersToScope(FD, PatternFD, *LIS, TemplateArgs))
return true;
}
```
Or earlier, in `Sema::InstantiateDefaultArgument` at `SemaTemplateInstantiateDecl.cpp:5279`, an explicit `assert(FD->isTemplateInstantiation())` so misuse fails loudly in debug builds.
Contributor guide
Assessment
This issue has not been assessed yet.