llvm / llvm/llvm-project

clang::Sema::InstantiateDefaultArgument crashes (null deref in addInstantiatedParametersToScope) when called from a Tooling client on a primary function template

Open
#197,263 1 comment 0 reactions 0 assignees View on GitHub
clang:frontend code-quality crash
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

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.