summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordhivael <dhivael.git@eno.space>2016-11-19 04:19:55 +0100
committerdhivael <dhivael.git@eno.space>2016-11-27 22:17:06 +0100
commit36a1d30e1231427ba56c91212df7f8c497c72fcf (patch)
tree24634c2c3ffa582078ff9c58db3ad8e01ac462ca
parente03a66cfd23c1adefbeb65eed2513092d09683b2 (diff)
rewrite noexcept checker with the new base class
-rw-r--r--Makefile31
-rw-r--r--static-noexcept.cpp795
-rw-r--r--tests/bad-template-instances.cpp18
-rw-r--r--tests/base-ctors.cpp9
-rw-r--r--tests/builtin-call.cpp5
-rw-r--r--tests/constructors.cpp40
-rw-r--r--tests/destructor.cpp42
-rw-r--r--tests/destructors.cpp66
-rw-r--r--tests/dyn-cast.cpp11
-rw-r--r--tests/dynamic_cast.cpp17
-rw-r--r--tests/extern-call.cpp26
-rw-r--r--tests/ignored-expr.cpp8
-rw-r--r--tests/ignored-expressions.cpp29
-rw-r--r--tests/implicit-noexcept.cpp201
-rw-r--r--tests/implied-noexcept.cpp46
-rw-r--r--tests/indirect-call.cpp12
-rw-r--r--tests/lambda.cpp43
-rw-r--r--tests/multiple-violations.cpp4
-rw-r--r--tests/nested-templates.cpp14
-rw-r--r--tests/new-delete.cpp49
-rw-r--r--tests/no-remark-on-specs.cpp10
-rw-r--r--tests/out-of-line-definitions.cpp90
-rw-r--r--tests/pseudo-dtor.cpp19
-rw-r--r--tests/r-implicit-noexcept-sys.h25
-rw-r--r--tests/recursion.cpp29
-rw-r--r--tests/redecl.cpp25
-rwxr-xr-xtests/run.sh1
-rw-r--r--tests/simple-call.cpp34
-rw-r--r--tests/static-initializer-throws.cpp3
-rw-r--r--tests/system-header.cpp3
-rw-r--r--tests/system-header.h16
-rw-r--r--tests/throw-stmt.cpp20
-rw-r--r--tests/throw.cpp20
-rw-r--r--tests/try-catch.cpp8
-rw-r--r--tests/typeid.cpp23
-rw-r--r--tests/violations-in-templates.cpp43
-rw-r--r--tests/wrapped.cpp6
37 files changed, 879 insertions, 962 deletions
diff --git a/Makefile b/Makefile
index 3a1f686..721943d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,30 +1,25 @@
-CXX := clang++
-CXXFLAGS := -g -fPIC -std=c++14 -fno-rtti -fno-exceptions
+CXX := g++
+CXXFLAGS := -Os -g -fPIC -std=c++14 -fno-rtti -fno-exceptions --coverage
-test-files := \
- tests/throw-stmt.cpp \
- tests/simple-call.cpp \
- tests/ignored-expr.cpp \
- tests/try-catch.cpp \
- tests/nested-templates.cpp \
- tests/wrapped.cpp \
- tests/constructors.cpp \
- tests/implied-noexcept.cpp \
- tests/destructor.cpp \
- tests/dyn-cast.cpp \
- tests/redecl.cpp \
- tests/implicit-noexcept.cpp
+test-files := $(wildcard tests/*.cpp)
static-noexcept.so: static-noexcept.cpp.o
$(CXX) $(CXXFLAGS) -shared -o $@ $?
-static-noexcept.cpp.o: static-noexcept.cpp
- $(CXX) $(CXXFLAGS) -c -o $@ $?
+static-noexcept.cpp.o: static-noexcept.cpp Makefile
+ $(CXX) $(CXXFLAGS) -c -o $@ $<
.PHONY: $(test-files)
$(test-files): static-noexcept.so
@echo "[TEST] $@"
- @tests/run.sh $@
+ @tests/run.sh $@ || true
.PHONY: check
check: $(test-files)
+
+cov-check:
+ lcov -z -d .
+ rm -rf cov-data cov-info
+ $(MAKE) check
+ geninfo -o cov-info --no-external --rc lcov_branch_coverage=1 .
+ genhtml -o cov-data --branch-coverage cov-info
diff --git a/static-noexcept.cpp b/static-noexcept.cpp
index 978dbd0..8662089 100644
--- a/static-noexcept.cpp
+++ b/static-noexcept.cpp
@@ -8,396 +8,186 @@
using namespace clang;
-struct MayThrowVisitor : public RecursiveASTVisitor<MayThrowVisitor> {
- Sema* sema;
- SourceLocation root;
- bool mayThrow;
- DiagnosticsEngine& diags;
-
- MayThrowVisitor(Sema& sema, SourceLocation root):
- sema(&sema), root(root), mayThrow(false), diags(sema.Diags)
+template<typename T>
+class onstack {
+public:
+ onstack(T& ref, T val):
+ ref(ref), old(std::move(ref))
{
+ ref = std::move(val);
}
- void errorAt(SourceLocation loc)
- {
- unsigned errorID = diags.getCustomDiagID(
- diags.Error,
- "function is noexcept, but contains expressions that aren't");
- unsigned noteOffenderID = diags.getCustomDiagID(
- diags.Note,
- "offending expression is here");
-
- if (!mayThrow)
- diags.Report(root, errorID);
-
- diags.Report(loc, noteOffenderID);
-
- mayThrow = true;
- }
+ onstack(onstack&&) = delete;
+ onstack& operator=(onstack&&) = delete;
- void badCallAt(SourceLocation loc)
+ ~onstack()
{
- unsigned noteOffenderID = diags.getCustomDiagID(
- diags.Note,
- "instantiated from here");
-
- diags.Report(loc, noteOffenderID);
+ ref = std::move(old);
}
- void badDeclAt(SourceLocation loc, const TemplateParameterList* params,
- const TemplateArgumentList* args)
- {
- unsigned noteOffenderID = diags.getCustomDiagID(
- diags.Note,
- "declared here %0");
-
- auto diag = diags.Report(loc, noteOffenderID);
- if (params)
- diag << sema->getTemplateArgumentBindingsText(params, *args);
- else
- diag << "";
- }
+private:
+ T& ref;
+ T old;
+};
- void diagnoseInstantationStack(FunctionDecl* callee)
+class V : public RecursiveASTVisitor<V> {
+public:
+ V(Sema& sema, bool remarkNoexceptCandidates):
+ sema(sema), checkDestructorOnConstruction(false),
+ remarkNoexceptCandidates(remarkNoexceptCandidates)
{
- if (callee->getPrimaryTemplate())
- badDeclAt(callee->getLocation(),
- callee->getPrimaryTemplate()->getTemplateParameters(),
- callee->getTemplateSpecializationArgs());
- else
- badDeclAt(callee->getLocation(), nullptr, nullptr);
-
- auto* parent = dyn_cast<ClassTemplateSpecializationDecl>(callee->getDeclContext());
- while (parent) {
- auto&& from = parent->getInstantiatedFrom();
- if (auto* ct = from.dyn_cast<ClassTemplateDecl*>())
- badDeclAt(parent->getLocation(),
- ct->getTemplateParameters(),
- &parent->getTemplateArgs());
- else if (auto* ps = from.dyn_cast<ClassTemplatePartialSpecializationDecl*>())
- badDeclAt(parent->getLocation(),
- ps->getTemplateParameters(),
- &parent->getTemplateArgs());
-
- parent = dyn_cast<ClassTemplateSpecializationDecl>(parent->getDeclContext());
- }
}
- bool VisitBinComma(BinaryOperator* b)
+ bool shouldVisitImplicitCode() const { return true; }
+ bool shouldVisitTemplateInstantiations() const { return true; }
+
+ bool TraverseFunctionDecl(FunctionDecl* fn)
{
- auto* tag = dyn_cast<CastExpr>(b->getLHS());
- if (!tag || tag->getCastKind() != CastKind::CK_ToVoid)
+ // builtins trigger this, among others
+ if (!fn->getType()->getAs<FunctionProtoType>())
return true;
-
- auto* arg = dyn_cast<CXXNoexceptExpr>(tag->getSubExpr());
- if (!arg)
+ if (!visitImplicitInstantiations &&
+ fn->getTemplateSpecializationKind() == TSK_ImplicitInstantiation)
return true;
- auto* val = dyn_cast<CXXBoolLiteralExpr>(arg->getOperand());
- if (val && val->getValue())
- return false;
-
+ inspectFunctionDecl<FunctionDecl, &RecursiveASTVisitor::TraverseFunctionDecl>(fn);
return true;
}
- bool VisitCXXConstructExpr(CXXConstructExpr* c)
+ bool TraverseCXXConstructorDecl(CXXConstructorDecl* fn)
{
- auto* ctor = c->getConstructor();
-
- if (c->isElidable())
+ if (fn->getParent()->getDescribedClassTemplate())
return true;
-
- if (ctor->isTemplateInstantiation()) {
- TraverseCXXConstructorDecl(ctor);
-
- if (mayThrow) {
- badCallAt(c->getExprLoc());
- diagnoseInstantationStack(ctor);
- return false;
- }
- }
-
- auto* ctorType = c->getConstructor()->getType()->castAs<FunctionProtoType>();
- if (!ctorType)
+ if (!visitImplicitInstantiations &&
+ fn->getTemplateSpecializationKind() == TSK_ImplicitInstantiation)
return true;
- if (ctorType->isNothrow(sema->Context))
- return true;
-
- if (ctor->hasBody()) {
- TraverseCXXConstructorDecl(cast<CXXConstructorDecl>(ctor->getDefinition()));
- if (!mayThrow)
- return false;
- }
-
- errorAt(c->getExprLoc());
- return false;
+ inspectFunctionDecl<CXXConstructorDecl, &RecursiveASTVisitor::TraverseCXXConstructorDecl>(fn);
+ return true;
}
- bool VisitCallExpr(CallExpr* c)
+ bool TraverseCXXConversionDecl(CXXConversionDecl* fn)
{
- if (c->isTypeDependent())
- return true;
-
- if (!c->getCalleeDecl()) {
- errorAt(c->getExprLoc());
- return false;
- }
-
- auto* callee = c->getDirectCallee();
- if (!callee) {
- errorAt(c->getExprLoc());
- return false;
- }
-
- if (callee->isTemplateInstantiation()) {
- TraverseFunctionDecl(callee);
-
- if (mayThrow) {
- badCallAt(c->getExprLoc());
- diagnoseInstantationStack(callee);
- return false;
- }
- }
-
- auto* calleeType = callee->getType()->castAs<FunctionProtoType>();
- if (!calleeType) {
- errorAt(c->getExprLoc());
- return false;
- }
-
- if (callee->isExternC())
+ if (fn->getParent()->getDescribedClassTemplate())
return true;
-
- if (calleeType->isNothrow(sema->Context))
+ if (!visitImplicitInstantiations &&
+ fn->getTemplateSpecializationKind() == TSK_ImplicitInstantiation)
return true;
- if (callee->hasBody()) {
- TraverseFunctionDecl(callee->getDefinition());
- if (!mayThrow)
- return false;
- }
-
- errorAt(c->getExprLoc());
- return false;
- }
-
- bool VisitCXXThrowExpr(CXXThrowExpr* t)
- {
- errorAt(t->getExprLoc());
+ inspectFunctionDecl<CXXConversionDecl, &RecursiveASTVisitor::TraverseCXXConversionDecl>(fn);
return true;
}
- bool VisitCXXTryStmt(CXXTryStmt* t)
+ bool TraverseCXXMethodDecl(CXXMethodDecl* fn)
{
- bool catchesAll = false;
- for (unsigned i = 0; i < t->getNumHandlers(); i++) {
- auto* h = t->getHandler(i);
- catchesAll |= h->getExceptionDecl() == nullptr;
- }
-
- if (!catchesAll)
- TraverseCompoundStmt(t->getTryBlock());
+ if (fn->getParent()->getDescribedClassTemplate())
+ return true;
+ if (!visitImplicitInstantiations &&
+ fn->getTemplateSpecializationKind() == TSK_ImplicitInstantiation)
+ return true;
- for (unsigned i = 0; i < t->getNumHandlers(); i++) {
- auto* h = t->getHandler(i);
- TraverseCXXCatchStmt(h);
- }
- return false;
+ inspectFunctionDecl<CXXMethodDecl, &RecursiveASTVisitor::TraverseCXXMethodDecl>(fn);
+ return true;
}
- bool VisitCXXDynamicCastExpr(CXXDynamicCastExpr* d)
+ bool TraverseCXXDestructorDecl(CXXDestructorDecl* fn)
{
- if (d->getType().isNull())
+ if (fn->getParent()->getDescribedClassTemplate())
return true;
- if (!d->getType()->isPointerType())
- errorAt(d->getExprLoc());
-
+ inspectFunctionDecl<CXXDestructorDecl, &RecursiveASTVisitor::TraverseCXXDestructorDecl>(fn);
return true;
}
-};
-
-class StaticNoexceptVisitor : public RecursiveASTVisitor<StaticNoexceptVisitor> {
-public:
- StaticNoexceptVisitor():
- sema(nullptr)
- {
- }
-
- Sema* sema;
- bool VisitFunctionDecl(FunctionDecl* fn)
+ bool TraverseCXXRecordDecl(CXXRecordDecl* r)
{
- if (sema->SourceMgr.getFileCharacteristic(fn->getLocation()) != SrcMgr::CharacteristicKind::C_User)
- return true;
-
- if (fn->isTemplateDecl())
- return true;
-
- if (!fn->hasBody())
- return true;
-
- auto* fnT = fn->getType()->castAs<FunctionProtoType>();
- if (!fnT)
- return true;
-
- if (!fnT->hasNoexceptExceptionSpec())
+ if (r->getDescribedClassTemplate())
return true;
-
- if (!fnT->isNothrow(sema->Context))
+ if (!visitedClasses.insert(r).second)
return true;
- MayThrowVisitor mtv(*sema, fn->getLocation());
-
- mtv.TraverseFunctionDecl(fn);
-
- return true;
+ return RecursiveASTVisitor::TraverseCXXRecordDecl(r);
}
- bool VisitFieldDecl(FieldDecl* f)
+ bool TraverseClassTemplatePartialSpecializationDecl(ClassTemplatePartialSpecializationDecl*)
{
- return checkAndDiagnoseThrowingDestructor(f->getType(), f->getLocation());
+ return true;
}
- bool VisitVarDecl(VarDecl* v)
+ bool TraverseClassTemplateSpecializationDecl(ClassTemplateSpecializationDecl* r)
{
- return checkAndDiagnoseThrowingDestructor(v->getType(), v->getLocation());
- }
+ if (!visitedClasses.insert(r).second)
+ return true;
- bool VisitCXXDeleteExpr(CXXDeleteExpr* d)
- {
- return checkAndDiagnoseThrowingDestructor(d->getDestroyedType(), d->getExprLoc());
+ return RecursiveASTVisitor::TraverseClassTemplateSpecializationDecl(r);
}
- bool VisitCXXTemporaryObjectExpr(CXXTemporaryObjectExpr* t)
+ bool TraverseExprWithCleanups(ExprWithCleanups* e)
{
- return checkAndDiagnoseThrowingDestructor(t->getType(), t->getExprLoc());
+ onstack<bool> s(checkDestructorOnConstruction, true);
+ return RecursiveASTVisitor::TraverseExprWithCleanups(e);
}
- bool checkAndDiagnoseThrowingDestructor(QualType type, SourceLocation loc)
+ bool TraverseVarDecl(VarDecl* v)
{
- if (type.isNull())
+ if (!visitedDecls.insert(v).second)
return true;
- const Type* baseType = type.getTypePtr();
- while (baseType->isArrayType())
- baseType = baseType->getArrayElementTypeNoTypeQual();
-
- if (!baseType->isRecordType())
- return true;
-
- auto* cls = baseType->getAsCXXRecordDecl();
- auto* dtor = cls->getDestructor();
- if (!dtor)
- return true;
-
- auto* fnT = dtor->getType()->castAs<FunctionProtoType>();
- if (!fnT)
- return true;
-
- if (fnT->getNoexceptSpec(sema->Context) != FunctionProtoType::NR_Throw)
- return true;
-
- auto* nee = fnT->getNoexceptExpr();
- if (auto* neeb = dyn_cast<BinaryOperator>(nee)) {
- auto* lhs = dyn_cast<CXXBoolLiteralExpr>(neeb->getLHS());
- auto* rhs = dyn_cast<CXXBoolLiteralExpr>(neeb->getRHS());
-
- if (lhs && rhs && !lhs->getValue() && !rhs->getValue())
- return true;
- }
-
- sema->Diags.Report(
- loc,
- sema->Diags.getCustomDiagID(
- DiagnosticsEngine::Error,
- "noexcept(false) destructors not supported yet"));
- return true;
+ onstack<bool> s(checkDestructorOnConstruction, true);
+ return RecursiveASTVisitor::TraverseVarDecl(v);
}
-};
-template<typename Derived>
-class NoexceptVisitor : public RecursiveASTVisitor<Derived> {
-public:
- bool shouldVisitImplicitCode() const { return true; }
-
- bool TraverseFunctionDecl(FunctionDecl* fn)
+ bool TraverseCXXNoexceptExpr(CXXNoexceptExpr*)
{
- return TraverseFunctionDeclHelper<
- FunctionDecl,
- &RecursiveASTVisitor<Derived>::TraverseFunctionDecl>(fn);
+ return true;
}
- bool TraverseCXXConstructorDecl(CXXConstructorDecl* fn)
+ bool TraverseDecltypeTypeLoc(DecltypeTypeLoc)
{
- return TraverseFunctionDeclHelper<
- CXXConstructorDecl,
- &RecursiveASTVisitor<Derived>::TraverseCXXConstructorDecl>(fn);
+ return true;
}
- bool TraverseCXXConversionDecl(CXXConversionDecl* fn)
+ bool TraverseCXXTypeidExpr(CXXTypeidExpr* t)
{
- return TraverseFunctionDeclHelper<
- CXXConversionDecl,
- &RecursiveASTVisitor<Derived>::TraverseCXXConversionDecl>(fn);
+ VisitCXXTypeidExpr(t);
+ return true;
}
- bool TraverseCXXMethodDecl(CXXMethodDecl* fn)
+ bool TraverseBinComma(BinaryOperator* b)
{
- return TraverseFunctionDeclHelper<
- CXXMethodDecl,
- &RecursiveASTVisitor<Derived>::TraverseCXXMethodDecl>(fn);
- }
+ auto* tag = dyn_cast<CastExpr>(b->getLHS());
+ if (!tag || tag->getCastKind() != CastKind::CK_ToVoid)
+ return RecursiveASTVisitor::TraverseBinComma(b);
- bool TraverseCXXDestructorDecl(CXXDestructorDecl* fn)
- {
- return TraverseFunctionDeclHelper<
- CXXDestructorDecl,
- &RecursiveASTVisitor<Derived>::TraverseCXXDestructorDecl>(fn);
- }
+ auto* arg = dyn_cast<CXXNoexceptExpr>(tag->getSubExpr());
+ if (!arg)
+ return RecursiveASTVisitor::TraverseBinComma(b);
- bool TraverseCXXRecordDecl(CXXRecordDecl* r)
- {
- if (r->getDescribedClassTemplate())
+ auto* val = dyn_cast<CXXBoolLiteralExpr>(arg->getOperand());
+ if (val && val->getValue())
return true;
- return RecursiveASTVisitor<Derived>::TraverseCXXRecordDecl(r);
- }
-
- bool TraverseClassTemplatePartialSpecializationDecl(ClassTemplatePartialSpecializationDecl*)
- {
- return true;
+ return RecursiveASTVisitor::TraverseBinComma(b);
}
bool VisitCXXThrowExpr(CXXThrowExpr* t)
{
- static_cast<Derived&>(*this).onThrowingExprFound(t);
+ diagnoseNoexceptViolationAt(t->getExprLoc());
return true;
}
bool VisitCallExpr(CallExpr* c)
{
- if (!c->getCalleeDecl())
+ if (isa<CXXPseudoDestructorExpr>(c->getCallee()))
return true;
auto* callee = c->getDirectCallee();
- if (!callee)
- return true;
-
- if (callee->isTemplateInstantiation())
- TraverseFunctionDecl(callee);
-
- if (implicitNoexceptFunctions.count(callee->getCanonicalDecl()))
- return true;
-
- auto* calleeType = callee->getType()->castAs<FunctionProtoType>();
-
- if (calleeType->getNoexceptSpec(sema.Context) == FunctionProtoType::NR_Nothrow)
+ if (!callee) {
+ diagnoseNoexceptViolationAt(c->getExprLoc());
return true;
+ }
- static_cast<Derived&>(*this).onThrowingExprFound(c);
+ assertFunctionNoexcept(callee, c);
return true;
}
@@ -405,79 +195,50 @@ public:
{
auto* ctor = c->getConstructor();
- do {
- if (implicitNoexceptFunctions.count(c->getConstructor()->getCanonicalDecl()))
- break;
-
- if (c->isElidable())
- break;
-
- auto* ctorType = c->getConstructor()->getType()->getAs<FunctionProtoType>();
- if (!ctorType)
- break;
-
- if (ctor->isTemplateInstantiation())
- TraverseFunctionDecl(ctor);
-
- if (ctorType->getNoexceptSpec(sema.Context) == FunctionProtoType::NR_Nothrow)
- break;
+ assertFunctionNoexcept(ctor, c);
- static_cast<Derived&>(*this).onThrowingExprFound(c);
- } while (0);
-
- if (checkDestructorOnConstruction) {
- auto* parent = c->getConstructor()->getParent();
- auto* dtor = parent->getDestructor();
- if (dtor && dtor->isTemplateInstantiation())
- TraverseFunctionDecl(dtor);
-
- checkDestructor(parent, c);
- }
+ if (checkDestructorOnConstruction)
+ assertDestructorNoexcept(c->getConstructor()->getParent(), c->getExprLoc());
return true;
}
- bool TraverseExprWithCleanups(ExprWithCleanups* e)
+ bool VisitParmVarDecl(ParmVarDecl* p)
{
- bool checkDestructorOnConstruction = this->checkDestructorOnConstruction;
- this->checkDestructorOnConstruction = true;
- RecursiveASTVisitor<Derived>::TraverseExprWithCleanups(e);
- this->checkDestructorOnConstruction = checkDestructorOnConstruction;
- return true;
- }
+ if (!doStrictChecking)
+ return true;
- bool TraverseVarDecl(VarDecl* v)
- {
- bool checkDestructorOnConstruction = this->checkDestructorOnConstruction;
- this->checkDestructorOnConstruction = true;
- RecursiveASTVisitor<Derived>::TraverseVarDecl(v);
- this->checkDestructorOnConstruction = checkDestructorOnConstruction;
- return true;
+ auto r = p->getType()->getAsCXXRecordDecl();
+ if (!r)
+ return true;
+
+ assertDestructorNoexcept(r, p->getLocation());
+ return RecursiveASTVisitor::VisitParmVarDecl(p);
}
bool VisitCXXNewExpr(CXXNewExpr* n)
{
if (n->getOperatorNew())
- checkFunction(n->getOperatorNew(), n);
+ assertFunctionNoexcept(n->getOperatorNew(), n);
if (n->getOperatorDelete())
- checkFunction(n->getOperatorDelete(), n);
+ assertFunctionNoexcept(n->getOperatorDelete(), n);
return true;
}
bool VisitCXXDeleteExpr(CXXDeleteExpr* d)
{
- if (!d->getDestroyedType()->isRecordType())
- return true;
+ assertFunctionNoexcept(d->getOperatorDelete(), d);
+
+ if (auto* r = d->getDestroyedType()->getAsCXXRecordDecl())
+ assertDestructorNoexcept(r, d->getExprLoc());
- checkDestructor(d->getDestroyedType()->getAsCXXRecordDecl(), d);
- checkFunction(d->getOperatorDelete(), d);
return true;
}
bool VisitCXXDynamicCastExpr(CXXDynamicCastExpr* d)
{
if (!d->getType()->isPointerType())
- static_cast<Derived&>(*this).onThrowingExprFound(d);
+ diagnoseNoexceptViolationAt(d->getExprLoc());
return true;
}
@@ -492,193 +253,263 @@ public:
return true;
if (u->getOpcode() == UnaryOperatorKind::UO_Deref)
- static_cast<Derived&>(*this).onThrowingExprFound(t);
+ diagnoseNoexceptViolationAt(t->getExprLoc());
return true;
}
bool TraverseLambdaExpr(LambdaExpr* l)
{
- TraverseCXXRecordDecl(l->getLambdaClass());
- RecursiveASTVisitor<Derived>::TraverseLambdaExpr(l);
+ this->TraverseCXXRecordDecl(l->getLambdaClass());
+ RecursiveASTVisitor::TraverseLambdaExpr(l);
return true;
}
-protected:
- Sema& sema;
- std::set<const FunctionDecl*>& implicitNoexceptFunctions;
-
- NoexceptVisitor(Sema& sema, std::set<const FunctionDecl*>& implicitNoexceptFunctions):
- sema(sema), implicitNoexceptFunctions(implicitNoexceptFunctions),
- checkDestructorOnConstruction(false)
+ bool VisitDeclRefExpr(DeclRefExpr* d)
{
+ {
+ onstack<bool> s(visitImplicitInstantiations, true);
+ RecursiveASTVisitor::TraverseDecl(d->getDecl());
+ }
+ return RecursiveASTVisitor::VisitDeclRefExpr(d);
}
private:
- bool checkDestructorOnConstruction;
+ Sema& sema;
+ std::set<const Decl*> visitedDecls;
+ std::set<const CXXRecordDecl*> visitedClasses;
+ std::set<const FunctionDecl*> provenNoexceptDefinitions;
+ std::set<const FunctionDecl*> recursionStack;
+ const FunctionDecl* currentFunction = nullptr;
+ bool checkDestructorOnConstruction = false;
+ bool stillImplicitlyNoexcept = true;
+ bool visitImplicitInstantiations = false;
+ bool doStrictChecking = false;
+ bool remarkNoexceptCandidates;
- template<typename T, bool (RecursiveASTVisitor<Derived>::*Traverse)(T*)>
- bool TraverseFunctionDeclHelper(T* fn)
+ template<typename FnT, bool (RecursiveASTVisitor::*Traverse)(FnT*)>
+ void inspectFunctionDecl(FnT* fn)
{
- if (fn->getTemplatedKind() == FunctionDecl::TK_FunctionTemplate)
- return true;
+ if (!std::is_same<CXXDestructorDecl, FnT>::value) {
+ if (fn->getTemplatedKind() == FunctionDecl::TK_FunctionTemplate)
+ return;
+ }
+ if (fn->getDefinition()) {
+ fn = cast<FnT>(fn->getDefinition());
+ if (!visitedDecls.insert(fn).second)
+ return;
+ }
- if (CXXMethodDecl* m = dyn_cast<CXXMethodDecl>(fn)) {
- if (m->getParent()->getDescribedTemplate())
- return true;
+ const FunctionProtoType* fnT = fn->getType()->template getAs<FunctionProtoType>();
+ fnT = sema.ResolveExceptionSpec(fn->getLocation(), fnT);
+
+ onstack<bool> s0(doStrictChecking, fnT->isNothrow(sema.Context));
+ onstack<bool> s1(stillImplicitlyNoexcept, true);
+ onstack<const FunctionDecl*> s2(currentFunction, fn->getDefinition());
+ onstack<bool> s3(visitImplicitInstantiations, true);
+
+ if (std::is_same<CXXDestructorDecl, FnT>::value)
+ checkFieldsOf(cast<CXXDestructorDecl>(fn)->getParent());
+
+ // definitions that do not produce a body are =default and =delete. since =delete is not a
+ // usable definition, and =default is only valid on special member functions, we can just
+ // believe the specs outright and have the rest of the checker handle any errors.
+ if (!fn->getDefinition() || !fn->hasBody()) {
+ if (fnT->isNothrow(sema.Context))
+ provenNoexceptDefinitions.insert(fn);
+ return;
}
- if (fn->getType().isNull())
- return true;
+ if (sema.SourceMgr.getFileCharacteristic(fn->getLocation()) != SrcMgr::CharacteristicKind::C_User &&
+ fnT->isNothrow(sema.Context)) {
+ // libstdc++ declares a number of functions as noexcept that cannot be proven by this checker.
+ // just allow system headers to do anything. if they crash, we can't do squat anyway.
+ } else {
+ recursionStack.insert(fn);
+ (this->*Traverse)(fn);
+ recursionStack.erase(fn);
+ }
- auto* fnT = fn->getType()->template getAs<FunctionProtoType>();
- if (!fnT)
- return true;
+ if (stillImplicitlyNoexcept) {
+ provenNoexceptDefinitions.insert(fn);
- if (implicitNoexceptFunctions.count(fn->getCanonicalDecl()))
- return true;
+ if (!doStrictChecking &&
+ !fnT->hasNoexceptExceptionSpec())
+ diagnoseCouldBeNoexcept(fn);
+ }
+ }
- static_cast<Derived&>(*this).template inspectFunctionDecl<T, Traverse>(fn);
- return true;
+ template<typename FnT>
+ void assertFunctionNoexcept(FnT* fn, Expr* expr)
+ {
+ if (!proveFunctionNoexcept(fn, expr->getExprLoc()))
+ diagnoseNoexceptViolationAt(expr->getExprLoc());
}
- template<typename ExprT>
- void checkFunction(FunctionDecl* f, ExprT* expr)
+ void assertDestructorNoexcept(CXXRecordDecl* r, SourceLocation loc)
{
- if (implicitNoexceptFunctions.count(f->getCanonicalDecl()))
- return;
+ if (!proveDestructorNoexcept(r, loc))
+ diagnoseNoexceptViolationAt(loc);
+ }
- auto* fnT = f->getType()->getAs<FunctionProtoType>();
- if (!fnT)
- return;
+ template<typename FnT>
+ bool proveFunctionNoexcept(FnT* fn, SourceLocation loc)
+ {
+ if (fn->isExternC())
+ return true;
- if (fnT->getNoexceptSpec(sema.Context) == FunctionProtoType::NR_Nothrow)
- return;
+ // if fn can recurse into itself via some arbitrary path, fn will be provably noexcept
+ // if and only if fn can be proven noexcept without the call:
+ // * if any other statement of fn is not provably noexcept, then the path fn -> ... -> fn
+ // need not be examined
+ // * if any functions on the path are not provably noexcept, fn the recursion need not be
+ // examined
+ // * if any of the non-recursive call graphs rooted in fn are not provably noexcept,
+ // the path need not be examined
+ //
+ // assuming that none of the above apply, we can assume:
+ // * all non-calling statements in fn itself are provably noexcept
+ // * all non-recursing call graphs rooted in fn are provably noexcept
+ // * all functions except fn are provably noexcept in any recursing call graph
+ // rooted in fn
+ // which lead to "fn is provably noexcept iff fn is provably noexcept".
+ // since the default noexceptness state is "noexcept except for throwing expressions",
+ // we can safely ignore the call.
+ if (recursionStack.count(fn))
+ return true;
+
+ if (provenNoexceptDefinitions.count(fn))
+ return true;
+
+ const FunctionProtoType* fnT = fn->getType()->template castAs<FunctionProtoType>();
+ fnT = sema.ResolveExceptionSpec(fn->getLocation(), fnT);
+
+ if (!fn->hasBody()) {
+ if (fnT->isNothrow(sema.Context))
+ return true;
+ }
+
+ recursionStack.insert(fn);
+ RecursiveASTVisitor::TraverseDecl(fn);
+ recursionStack.erase(fn);
- static_cast<Derived&>(*this).onThrowingExprFound(expr);
+ return provenNoexceptDefinitions.count(fn->getDefinition()) ||
+ fnT->isNothrow(sema.Context);
}
- template<typename ExprT>
- void checkDestructor(CXXRecordDecl* r, ExprT* expr)
+ bool proveDestructorNoexcept(CXXRecordDecl* r, SourceLocation loc)
{
auto* dtor = r->getDestructor();
if (!dtor)
- return;
-
- checkFunction(dtor, expr);
- }
-};
+ return true;
-class ImplicitNoexceptMarker : public NoexceptVisitor<ImplicitNoexceptMarker> {
-public:
- ImplicitNoexceptMarker(Sema& sema, std::set<const FunctionDecl*>& implicitNoexceptFunctions,
- bool remarkCandidates):
- NoexceptVisitor(sema, implicitNoexceptFunctions),
- remarkCandidates(remarkCandidates)
- {
+ return proveFunctionNoexcept(dtor, loc);
}
- template<typename FnT, bool (RecursiveASTVisitor::*Traverse)(FnT*)>
- void inspectFunctionDecl(FnT* fn)
+ void diagnoseCouldBeNoexcept(FunctionDecl* callee)
{
- if (fn->isExternC()) {
- implicitNoexceptFunctions.insert(fn->getCanonicalDecl());
+ if (!remarkNoexceptCandidates)
return;
- }
- if (!fn->hasBody())
+ if (sema.SourceMgr.getFileCharacteristic(callee->getLocation()) != SrcMgr::CharacteristicKind::C_User)
return;
- auto* fnT = fn->getType()->template castAs<FunctionProtoType>();
+ unsigned couldBe = sema.Diags.getCustomDiagID(
+ DiagnosticsEngine::Remark,
+ "function %q0 could be noexcept");
- if (isa<CXXDestructorDecl>(fn)) {
- auto* fnT = fn->getType()->template castAs<FunctionProtoType>();
- if (!fnT->hasNoexceptExceptionSpec() ||
- fnT->getNoexceptSpec(sema.Context) == FunctionProtoType::NR_Nothrow) {
- // destructors are noexcept by default. no diagnostic.
- implicitNoexceptFunctions.insert(fn->getCanonicalDecl());
- }
- return;
- }
+ sema.Diags.Report(callee->getLocation(), couldBe)
+ << callee;
+ }
- bool stillImplicitlyNoexcept = this->stillImplicitlyNoexcept;
- this->stillImplicitlyNoexcept = true;
+ void diagnoseNoexceptViolationAt(SourceLocation loc)
+ {
+ bool diagnoseFunctionLocation = stillImplicitlyNoexcept;
- (this->*Traverse)(fn);
- do {
- if (this->stillImplicitlyNoexcept) {
- implicitNoexceptFunctions.insert(fn->getCanonicalDecl());
+ stillImplicitlyNoexcept = false;
- if (auto* ctor = dyn_cast<CXXConstructorDecl>(fn)) {
- if (ctor->isDefaulted())
- break;
- }
+ if (!doStrictChecking)
+ return;
- if (!fnT->hasNoexceptExceptionSpec() &&
- fnT->getNoexceptSpec(sema.Context) != FunctionProtoType::NR_Nothrow)
- diagnoseCouldBeNoexcept(fn);
- }
- } while (0);
+ assert(currentFunction && "global context cannot be noexcept"); // LCOV_EXCL_LINE
- this->stillImplicitlyNoexcept = stillImplicitlyNoexcept;
- }
+ unsigned errorID = sema.Diags.getCustomDiagID(
+ DiagnosticsEngine::Error,
+ "function %q0 is noexcept, but contains expressions that aren't");
+ unsigned noteOffenderID = sema.Diags.getCustomDiagID(
+ DiagnosticsEngine::Note,
+ "offending expression is here");
+ unsigned instantiatedFromID = sema.Diags.getCustomDiagID(
+ DiagnosticsEngine::Note,
+ "instantiated from here");
- template<typename ExprT>
- void onThrowingExprFound(ExprT* expr)
- {
- stillImplicitlyNoexcept = false;
- }
+ if (diagnoseFunctionLocation) {
+ sema.Diags.Report(currentFunction->getLocation(), errorID)
+ << currentFunction;
+ if (currentFunction->getPointOfInstantiation().isValid())
+ sema.Diags.Report(currentFunction->getPointOfInstantiation(), instantiatedFromID);
+ }
-private:
- bool stillImplicitlyNoexcept = true;
- bool remarkCandidates;
+ sema.Diags.Report(loc, noteOffenderID);
+ }
- void diagnoseCouldBeNoexcept(FunctionDecl* callee)
+ void checkFieldsOf(CXXRecordDecl* r)
{
- if (!remarkCandidates)
+ if (r->isUnion())
return;
- if (sema.SourceMgr.getFileCharacteristic(callee->getLocation()) != SrcMgr::CharacteristicKind::C_User)
+ auto* dtor = r->getDestructor();
+ auto* fnT = dtor->getType()->template castAs<FunctionProtoType>();
+ fnT = sema.ResolveExceptionSpec(dtor->getLocation(), fnT);
+ if (!fnT->isNothrow(sema.Context)) {
+ stillImplicitlyNoexcept = false;
return;
+ }
- unsigned couldBe = sema.Diags.getCustomDiagID(
- DiagnosticsEngine::Remark,
- "could be noexcept %0");
- unsigned declaredHere = sema.Diags.getCustomDiagID(
- DiagnosticsEngine::Remark,
- "declared here %0");
+ for (auto b : r->bases()) {
+ auto* base = b.getType()->getAsCXXRecordDecl();
+ if (!base)
+ continue;
- {
- auto diag = sema.Diags.Report(callee->getLocation(), couldBe);
- if (auto* pt = callee->getPrimaryTemplate())
- diag << sema.getTemplateArgumentBindingsText(
- pt->getTemplateParameters(),
- *callee->getTemplateSpecializationArgs());
- else
- diag << "";
+ checkDestructorFor(base, b.getLocStart(), true, r);
}
- auto* parent = dyn_cast<ClassTemplateSpecializationDecl>(callee->getDeclContext());
- while (parent) {
- auto&& from = parent->getInstantiatedFrom();
+ for (auto* f : r->fields()) {
+ auto* base = sema.Context.getBaseElementType(f->getType())->getAsCXXRecordDecl();
+ if (!base)
+ continue;
- TemplateParameterList* params;
+ checkDestructorFor(base, f->getLocation(), false, r);
+ }
+ }
- if (auto* ct = from.dyn_cast<ClassTemplateDecl*>())
- params = ct->getTemplateParameters();
- else if (auto* ps = from.dyn_cast<ClassTemplatePartialSpecializationDecl*>())
- params = ps->getTemplateParameters();
- else
- break;
+ void checkDestructorFor(CXXRecordDecl* r, SourceLocation loc, bool isBase, CXXRecordDecl* parent)
+ {
+ auto* d = r->getDestructor();
+ if (!d)
+ return;
+
+ RecursiveASTVisitor::TraverseDecl(r);
- auto diag = sema.Diags.Report(parent->getLocation(), declaredHere);
- if (params)
- diag << sema.getTemplateArgumentBindingsText(params, parent->getTemplateArgs());
- else
- diag << "";
+ if (provenNoexceptDefinitions.count(d->getDefinition()))
+ return;
- parent = dyn_cast<ClassTemplateSpecializationDecl>(parent->getDeclContext());
+ auto* dT = d->getType()->castAs<FunctionProtoType>();
+ if (!d->getDefinition() || d->isDefaulted()) {
+ if (dT->isNothrow(sema.Context))
+ return;
}
+
+ unsigned errorID = sema.Diags.getCustomDiagID(
+ DiagnosticsEngine::Error,
+ "%0 of type %q1 violates destructor noexcept specification of %q2");
+
+ sema.Diags.Report(loc, errorID)
+ << (isBase ? "base class" : "field")
+ << r
+ << parent;
+
+ stillImplicitlyNoexcept = false;
}
};
@@ -691,37 +522,23 @@ public:
virtual void HandleTranslationUnit(ASTContext &Context)
{
- std::set<const FunctionDecl*> implicits;
-
- for (;;) {
- auto implicitAtStart = implicits.size();
-
- ImplicitNoexceptMarker(*sema, implicits, remarkCandidates)
- .TraverseDecl(Context.getTranslationUnitDecl());
-
- if (implicits.size() == implicitAtStart)
- break;
- }
-
- Visitor.TraverseDecl(Context.getTranslationUnitDecl());
+ V(*sema, remarkCandidates)
+ .TraverseDecl(Context.getTranslationUnitDecl());
}
virtual void InitializeSema(Sema& sema)
{
this->sema = &sema;
- Visitor.sema = &sema;
}
virtual void ForgetSema()
{
this->sema = nullptr;
- Visitor.sema = nullptr;
}
private:
Sema* sema;
bool remarkCandidates;
- StaticNoexceptVisitor Visitor;
};
struct StaticNoexcept : public PluginASTAction {
diff --git a/tests/bad-template-instances.cpp b/tests/bad-template-instances.cpp
new file mode 100644
index 0000000..5ba1115
--- /dev/null
+++ b/tests/bad-template-instances.cpp
@@ -0,0 +1,18 @@
+// args: -Rcandidates
+
+// expected-no-diagnostics
+
+template<typename T>
+struct X {
+ template<typename Fn>
+ void f(Fn fn) const noexcept(noexcept(fn(*((const T*)0)))) {}
+
+ template<typename Fn>
+ void f(Fn fn) noexcept(noexcept(fn(*((T*)0)))) {}
+};
+
+X<int> x() noexcept { return {}; }
+
+void t0() noexcept {
+ x().f([] (int&) noexcept {});
+}
diff --git a/tests/base-ctors.cpp b/tests/base-ctors.cpp
new file mode 100644
index 0000000..48af7c2
--- /dev/null
+++ b/tests/base-ctors.cpp
@@ -0,0 +1,9 @@
+// args: -Rcandidates
+
+struct S1 {
+ S1() noexcept(false);
+};
+
+struct S2 : S1 {
+ S2() {} // expected-no-diagnostics
+};
diff --git a/tests/builtin-call.cpp b/tests/builtin-call.cpp
new file mode 100644
index 0000000..582c7d5
--- /dev/null
+++ b/tests/builtin-call.cpp
@@ -0,0 +1,5 @@
+// args: -Rcandidates
+
+void x(int* i) { // expected-remark {{could be noexcept}}
+ __atomic_fetch_or(i, 8, 0);
+}
diff --git a/tests/constructors.cpp b/tests/constructors.cpp
deleted file mode 100644
index ddb89e8..0000000
--- a/tests/constructors.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-template<typename T>
-struct remove_reference {
- typedef T type;
-};
-template<typename T>
-struct remove_reference<T&> {
- typedef T type;
-};
-
-template<typename _Tp>
-constexpr typename remove_reference<_Tp>::type&&
-move(_Tp&& __t) noexcept
-{ return static_cast<typename remove_reference<_Tp>::type&&>(__t); }
-
-template<typename T>
-struct S { // expected-note {{declared here}}
- S(T t) noexcept: // expected-note {{declared here}}
- t(move(t)) // expected-note {{is here}}
- {}
-
- T t;
-};
-
-struct M {
- M() noexcept;
- M(M&&) noexcept(false);
-};
-
-struct K {
- K(M&& m) noexcept: // expected-error {{is noexcept}}
- m(move(m)) // expected-note {{is here}}
- {}
- M m;
-};
-
-void t0() noexcept // expected-error {{is noexcept}}
-{
- S<int> s(0);
- S<M> t(M{}); // expected-note {{instantiated from here}}
-}
diff --git a/tests/destructor.cpp b/tests/destructor.cpp
deleted file mode 100644
index fa628ef..0000000
--- a/tests/destructor.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-struct S {
- ~S() noexcept(false) {
- throw 1;
- }
-
- S() = default;
- S(int, int) noexcept {}
-};
-
-struct T {
- S s; // expected-error {{not supported yet}}
-};
-
-struct U {
- ~U() noexcept(false && false) {
- }
-};
-
-void t0() noexcept
-{
- U u;
- U u2[10];
- U u3[10][10];
- delete new U;
- delete[] new U[2];
-
- (void) (
- (void) S(0, 0), // expected-error {{not supported yet}}
- (void) S(1, 1), // expected-error {{not supported yet}}
- 1);
-
- S s; // expected-error {{not supported yet}}
- S s2[10]; // expected-error {{not supported yet}}
- S s3[10][10]; // expected-error {{not supported yet}}
- delete new S; // expected-error {{not supported yet}}
- delete[] new S[2]; // expected-error {{not supported yet}}
-
- (void) (
- (void) S(0, 0), // expected-error {{not supported yet}}
- (void) S(1, 1), // expected-error {{not supported yet}}
- 1);
-}
diff --git a/tests/destructors.cpp b/tests/destructors.cpp
new file mode 100644
index 0000000..593bf4d
--- /dev/null
+++ b/tests/destructors.cpp
@@ -0,0 +1,66 @@
+// args: -Rcandidates
+
+struct S0 {
+ ~S0() = default;
+};
+
+void t0() noexcept {
+ S0 s;
+}
+
+struct S1 {
+ ~S1() { // expected-error {{is noexcept}}
+ throw 1; // expected-note {{is here}}
+ }
+};
+
+struct S2 {
+ ~S2() noexcept(false) {}
+};
+
+void t1() noexcept { // expected-error {{is noexcept}}
+ S2 s; // expected-note {{is here}}
+ S2(); // expected-note {{is here}}
+}
+
+struct S3 {
+ S2 s;
+};
+
+struct S4 {
+ ~S4() noexcept(false) {
+ }
+};
+
+void t2(S4 s) noexcept { // expected-error {{is noexcept}} // expected-note {{is here}}
+}
+
+union U0 {
+ ~U0() {}
+};
+
+template<typename>
+struct T;
+
+template<>
+struct T<int> {
+ S2 s;
+};
+
+struct S5 {
+ ~S5() throw(int);
+};
+
+struct S6 :
+ T<int>, // expected-error {{violates destructor noexcept spec}}
+ S5 // expected-error {{violates destructor noexcept spec}}
+{
+ ~S6() noexcept {}
+};
+
+struct S7 {
+};
+
+void t3() noexcept {
+ S7 s; // destroys struct without implicitly referenced dtor
+}
diff --git a/tests/dyn-cast.cpp b/tests/dyn-cast.cpp
deleted file mode 100644
index 24a1615..0000000
--- a/tests/dyn-cast.cpp
+++ /dev/null
@@ -1,11 +0,0 @@
-struct X {
- virtual ~X() {}
-};
-
-struct Y : X {
-};
-
-void t0(X* x) noexcept // expected-error {{is noexcept}}
-{
- (void) dynamic_cast<Y&>(*x); // expected-note {{is here}}
-}
diff --git a/tests/dynamic_cast.cpp b/tests/dynamic_cast.cpp
new file mode 100644
index 0000000..0bdce76
--- /dev/null
+++ b/tests/dynamic_cast.cpp
@@ -0,0 +1,17 @@
+// args: -Rcandidates
+
+struct T0 {
+ T0() { throw 1; }
+
+ virtual ~T0() {}
+};
+
+struct T1 : T0 {
+};
+
+void t0(T0* a) { // expected-remark {{could be noexcept}}
+ (void) dynamic_cast<T1*>(a);
+}
+void t1(T0& a) noexcept { // expected-error {{is noexcept}}
+ (void) dynamic_cast<T1&>(a); // expected-note {{is here}}
+}
diff --git a/tests/extern-call.cpp b/tests/extern-call.cpp
new file mode 100644
index 0000000..86c1757
--- /dev/null
+++ b/tests/extern-call.cpp
@@ -0,0 +1,26 @@
+// args: -Rcandidates
+
+extern "C" void ext_c();
+void ext_cpp();
+void ext_cpp_ne() noexcept;
+
+void t0() { // expected-remark {{could be noexcept}}
+ ext_c();
+}
+void t1() noexcept {
+ ext_c();
+}
+
+void t2() {
+ ext_cpp();
+}
+void t3() noexcept { // expected-error {{is noexcept}}
+ ext_cpp(); // expected-note {{is here}}
+}
+
+void t4() { // expected-remark {{could be noexcept}}
+ ext_cpp_ne();
+}
+void t5() noexcept {
+ ext_cpp_ne();
+}
diff --git a/tests/ignored-expr.cpp b/tests/ignored-expr.cpp
deleted file mode 100644
index 83f73ab..0000000
--- a/tests/ignored-expr.cpp
+++ /dev/null
@@ -1,8 +0,0 @@
-void throws()
-{
-}
-
-void t0() noexcept
-{
- ((void) noexcept(true), throws()); // expected-no-diagnostics
-}
diff --git a/tests/ignored-expressions.cpp b/tests/ignored-expressions.cpp
new file mode 100644
index 0000000..9fcf866
--- /dev/null
+++ b/tests/ignored-expressions.cpp
@@ -0,0 +1,29 @@
+// args: -Rcandidates
+
+void throws();
+
+void t0() { // expected-remark {{could be noexcept}}
+ (void) noexcept(throws());
+ (void) decltype(throws())();
+
+ ((void) noexcept(true), throws());
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-value"
+void t1() noexcept { // expected-error {{is noexcept}}
+ (1, throws()); // expected-note {{is here}}
+}
+void t2() noexcept { // expected-error {{is noexcept}}
+ ((int) noexcept(true), throws()); // expected-note {{is here}}
+}
+void t3() noexcept { // expected-error {{is noexcept}}
+ ((void) 1, throws()); // expected-note {{is here}}
+}
+void t4() noexcept { // expected-error {{is noexcept}}
+ ((void) noexcept(1), throws()); // expected-note {{is here}}
+}
+void t5() noexcept { // expected-error {{is noexcept}}
+ ((void) noexcept(false), throws()); // expected-note {{is here}}
+}
+#pragma clang diagnostic pop
diff --git a/tests/implicit-noexcept.cpp b/tests/implicit-noexcept.cpp
deleted file mode 100644
index 68a9b57..0000000
--- a/tests/implicit-noexcept.cpp
+++ /dev/null
@@ -1,201 +0,0 @@
-// args: -Rcandidates
-
-void noexcept0() {} // expected-remark {{could be noexcept}}
-
-template<bool B>
-void noexcept1() {} // expected-remark {{could be noexcept [with B = true]}}
-
-void noexcept2() // expected-remark {{could be noexcept}}
-{
- noexcept1<true>();
-}
-
-struct S0 {
- S0() {} // expected-remark {{could be noexcept}}
- S0(int) {} // expected-remark {{could be noexcept}}
- ~S0() {}
-
- operator int() { return 0; } // expected-remark {{could be noexcept}}
- void operator()() {} // expected-remark {{could be noexcept}}
-
- void* operator new(unsigned long) noexcept;
- void operator delete(void*) noexcept;
-};
-
-template<bool B>
-struct S1 { // expected-remark {{declared here [with B = true]}} expected-remark {{declared here [with B = true]}}
- S1() {} // expected-remark {{could be noexcept}}
- ~S1() {}
-
- struct I0 {
- I0() {} // expected-remark {{could be noexcept}}
- };
-
- struct I1 {
- I1() {}
- };
-
- operator int() { return 0; }
- operator short() { return 0; } // expected-remark {{could be noexcept}}
-};
-
-struct S2 {
- S2() = default;
-
- S0 s;
-};
-
-void noexcept3() // expected-remark {{could be noexcept}}
-{
- S0();
- S0(0);
- S1<true>();
- S1<true>::I0();
- (void) short(S1<true>());
- delete new S0();
- S0().operator int();
- S0()();
-}
-
-extern "C" void noexcept4();
-
-void noexcept5() { noexcept4(); } // expected-remark {{could be noexcept}}
-
-
-
-void throw0() { throw 1; }
-
-template<bool B>
-void throw1() { throw 1; }
-
-void throw2()
-{
- throw1<true>();
-}
-
-extern void throw3();
-
-void throw4() { throw3(); }
-
-struct T0 {
- T0() { throw 1; }
- T0(int) { throw 1; }
- ~T0() {}
-
- operator int() { throw 1; }
-};
-
-struct T1 {
- T1() noexcept {}
- T1(int) noexcept {}
- ~T1() noexcept(false && false) { throw 1; }
-
- T1(T1&&);
-
- operator int() { throw 1; }
-};
-
-struct T2 {
- void* operator new(unsigned long);
- void operator delete(void*);
-
- void operator()() { throw 1; }
-};
-
-template<bool>
-struct T3 {
- T3() { throw 1; }
- ~T3() { throw 1; }
-};
-
-void throw5() { T0(); }
-void throw6() { T0(0); }
-void throw7() { new T0; }
-void throw8() { new T0(0); }
-void throw9() { new T0[10]; }
-void throw10() { new T0[10]; }
-void throw11(T1* a) { delete a; }
-void throw12(T1* a) { delete[] a; }
-void throw13() { T1(); }
-void throw14() { T1(0); }
-void throw15() { new T2; }
-void throw16(T2* a) { delete a; }
-void throw17() { T3<true>(); }
-void throw18() { T1 a; }
-
-struct T4 {
- T4() { throw 1; }
-
- virtual ~T4() {}
-};
-
-struct T5 : T4 {
- using T4::T4;
-};
-
-void noexcept6(T4* a) { (void) dynamic_cast<T5*>(a); } // expected-remark {{could be noexcept}}
-void throw19(T4& a) { (void) dynamic_cast<T5&>(a); }
-void throw20() = delete;
-void throw21() { T2()(); }
-void throw22() { T5(); }
-
-template<char...>
-int operator"" _a() { return 0; } // expected-remark {{could be noexcept}}
-
-void noexcept7() { 123_a; } // expected-remark {{could be noexcept}}
-
-template<char...>
-int operator"" _b() { throw 0; }
-
-void throw23() { 123_b; }
-
-#include <typeinfo>
-
-void noexcept8(T4& a) { (void) typeid(a); } // expected-remark {{could be noexcept}}
-void throw24(T4* a) { (void) typeid(*a); }
-
-template<typename Fn>
-void noexcept9(Fn fn) { fn(); } // expected-remark {{could be noexcept}}
-
-void noexcept10() { noexcept9([] {}); } // expected-remark {{could be noexcept}}
-
-template<typename Fn>
-void noexcept11(Fn fn) { fn(0); } // expected-remark {{could be noexcept}}
-
-void noexcept12() { noexcept11([] (auto) {}); } // expected-remark {{could be noexcept}}
-
-template<typename Fn>
-void throw25(Fn fn) { fn(); }
-
-void throw26() { throw25([] { throw 0; }); }
-
-template<typename Fn>
-void noexcept13(Fn fn) { fn(); } // expected-remark {{could be noexcept}}
-
-void throw28() { noexcept13([x=T0()] () noexcept { }); }
-
-template<typename Fn>
-void throw29(Fn fn) { Fn x = std::move(fn); x(); }
-
-void throw30() { throw29([x=T1()] () noexcept { }); }
-
-template<typename>
-struct S3 {
- void dont_check();
-};
-
-template<typename T>
-void S3<T>::dont_check() {}
-
-void noexcept14() noexcept
-{
- (void) typeid(int);
-}
-
-template<typename>
-struct S4;
-
-template<typename P>
-struct S4<P*> : P {
- void foo() { __atomic_fetch_or(this, 8, 0); }
-};
diff --git a/tests/implied-noexcept.cpp b/tests/implied-noexcept.cpp
deleted file mode 100644
index 17871ef..0000000
--- a/tests/implied-noexcept.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-template<typename T>
-struct S0 {
- T t;
-
- S0(T t): t(t) {}
-};
-
-template<typename T>
-struct S1 {
- T t;
-
- S1(T t);
-};
-
-template<template<typename> class S, typename T>
-struct H { // expected-note {{declared here}}
- S<T> t;
-
- H(T t): // expected-note {{declared here}}
- t(t) // expected-note {{is here}}
- {}
-};
-
-H<S0, int> t0() noexcept
-{
- return H<S0, int>(0);
-}
-
-H<S1, int> t1() noexcept // expected-error {{is noexcept}}
-{
- return H<S1, int>(0); // expected-note {{from here}}
-}
-
-
-void b0() {}
-void b1();
-
-void t2() noexcept
-{
- b0();
-}
-
-void t3() noexcept // expected-error {{is noexcept}}
-{
- b1(); // expected-note {{is here}}
-}
diff --git a/tests/indirect-call.cpp b/tests/indirect-call.cpp
new file mode 100644
index 0000000..db1eed0
--- /dev/null
+++ b/tests/indirect-call.cpp
@@ -0,0 +1,12 @@
+extern "C" void c_call();
+void bar() noexcept(true);
+
+void t0() noexcept // expected-error {{is noexcept}}
+{
+ (&c_call)(); // expected-note {{is here}}
+}
+
+void t1() noexcept // expected-error {{is noexcept}}
+{
+ (&bar)(); // expected-note {{is here}}
+}
diff --git a/tests/lambda.cpp b/tests/lambda.cpp
new file mode 100644
index 0000000..e2bbcbd
--- /dev/null
+++ b/tests/lambda.cpp
@@ -0,0 +1,43 @@
+// args: -Rcandidates
+
+template<typename Fn>
+void t0(Fn f) noexcept {
+ f();
+}
+
+template<typename Fn>
+void t1(Fn f) noexcept {
+ f(1);
+}
+
+void t2() noexcept {
+ t0([] {}); // expected-remark {{could be noexcept}}
+ t1([] (auto) {}); // expected-remark {{could be noexcept}}
+ t0([] () noexcept {});
+ t1([] (auto) noexcept {});
+}
+
+struct S0 {
+ ~S0() noexcept(false);
+};
+
+struct S1 {
+ S1() = default;
+ S1(S1&&) noexcept(false);
+};
+
+void t3() noexcept { // expected-error {{is noexcept}}
+ // lambda body could be noexcept, dtors of S0 arent't though
+ auto l0 = [ // expected-note {{is here}} expected-note {{is here}} expected-remark {{could be noexcept}} expected-error {{is noexcept}}
+ x=S0() // expected-note {{is here}} expected-note {{is here}}
+ ] {};
+
+ // lambda body could be noexcept, ctors of S0 arent't though
+ auto l1 = [ // expected-note {{is here}} expected-remark {{could be noexcept}}
+ x=S1() // expected-note {{is here}}
+ ] {};
+
+ auto l2 = [] {
+ S0(); // expected-note {{is here}}
+ };
+}
diff --git a/tests/multiple-violations.cpp b/tests/multiple-violations.cpp
new file mode 100644
index 0000000..0f9f10f
--- /dev/null
+++ b/tests/multiple-violations.cpp
@@ -0,0 +1,4 @@
+void t0() noexcept { // expected-error {{is noexcept}}
+ throw 1; // expected-note {{is here}}
+ throw 1; // expected-note {{is here}}
+}
diff --git a/tests/nested-templates.cpp b/tests/nested-templates.cpp
deleted file mode 100644
index 235ca74..0000000
--- a/tests/nested-templates.cpp
+++ /dev/null
@@ -1,14 +0,0 @@
-template<typename V1 = void>
-struct foo { // expected-note {{declared here}}
- template<typename V2 = void>
- struct basic_string { // expected-note {{declared here}}
- const char* data() const { // expected-note {{declared here}}
- throw 1; // expected-note {{is here}}
- }
- };
-};
-
-void t0() noexcept // expected-error {{is noexcept}}
-{
- foo<>::basic_string<>{}.data(); // expected-note {{instantiated from here}}
-}
diff --git a/tests/new-delete.cpp b/tests/new-delete.cpp
new file mode 100644
index 0000000..9143ef6
--- /dev/null
+++ b/tests/new-delete.cpp
@@ -0,0 +1,49 @@
+// args: -Rcandidates
+
+struct S {
+ ~S() noexcept(false) {
+ throw 1;
+ }
+
+ S() = default;
+ S(int, int) noexcept {}
+};
+
+template<typename X>
+struct U {
+ X a, b;
+};
+
+template<typename S>
+struct W {
+ union U {
+ ~U() noexcept {}
+
+ int a;
+ S b;
+ } u;
+};
+
+void t0() noexcept // expected-error {{is noexcept}}
+{
+ S s; // expected-note {{is here}}
+ S s2[10]; // expected-note {{is here}}
+ S s3[10][10]; // expected-note {{is here}}
+ delete // expected-note {{is here}}
+ new S; // expected-note {{is here}}
+ delete[] // expected-note {{is here}}
+ new S[2]; // expected-note {{is here}}
+
+ (void) (
+ (void) S(0, 0), // expected-note {{is here}}
+ (void) S(1, 1), // expected-note {{is here}}
+ 1);
+
+ U<S> u; // expected-note {{is here}}
+ W<S> w;
+}
+
+void t1(int* i) noexcept {
+ delete i;
+ delete[] i;
+}
diff --git a/tests/no-remark-on-specs.cpp b/tests/no-remark-on-specs.cpp
new file mode 100644
index 0000000..14b6656
--- /dev/null
+++ b/tests/no-remark-on-specs.cpp
@@ -0,0 +1,10 @@
+// args: -Rcandidates
+
+void t0() {} // expected-remark {{could be noexcept}}
+void t1() noexcept(false) {}
+
+struct S0 {
+ S0() noexcept(false) {}
+ void f() noexcept(false) {}
+ operator int() noexcept(false) { return 0; }
+};
diff --git a/tests/out-of-line-definitions.cpp b/tests/out-of-line-definitions.cpp
new file mode 100644
index 0000000..792c485
--- /dev/null
+++ b/tests/out-of-line-definitions.cpp
@@ -0,0 +1,90 @@
+// args: -Rcandidates
+
+template<typename>
+struct S0 {
+ S0();
+ ~S0();
+
+ void f0();
+ void f1() noexcept;
+
+ operator short();
+ operator int() noexcept;
+};
+
+template<typename T>
+S0<T>::S0() {
+}
+
+template<typename T>
+S0<T>::~S0() {
+ throw 1;
+}
+
+template<typename T>
+void S0<T>::f0() {}
+
+template<typename T>
+void S0<T>::f1() noexcept {
+ throw 1;
+}
+
+template<typename T>
+S0<T>::operator short() {
+ throw 1;
+}
+
+template<typename T>
+S0<T>::operator int() noexcept {
+ throw 1;
+}
+
+
+struct S1 {
+ template<typename T>
+ S1(T) noexcept;
+
+ S1(int) noexcept;
+ S1(short);
+ S1(char);
+
+ ~S1();
+
+ void f0();
+ void f1() noexcept;
+
+ operator short();
+ operator int() noexcept;
+};
+
+template<typename T>
+S1::S1(T) noexcept {
+ throw 1;
+}
+
+S1::S1(int) noexcept { // expected-error {{is noexcept}}
+ throw 1; // expected-note {{is here}}
+}
+
+S1::S1(short) {} // expected-remark {{could be noexcept}}
+S1::S1(char) {
+ throw 1;
+}
+
+S1::~S1() { // expected-error {{is noexcept}}
+ throw 1; // expected-note {{is here}}
+}
+
+void S1::f0() {} // expected-remark {{could be noexcept}}
+
+void S1::f1() noexcept { // expected-error {{is noexcept}}
+ throw 1; // expected-note {{is here}}
+}
+
+S1::operator short() {
+ throw 1;
+}
+
+S1::operator int() noexcept { // expected-error {{is noexcept}}
+ throw 1; // expected-note {{is here}}
+}
diff --git a/tests/pseudo-dtor.cpp b/tests/pseudo-dtor.cpp
new file mode 100644
index 0000000..7001f3a
--- /dev/null
+++ b/tests/pseudo-dtor.cpp
@@ -0,0 +1,19 @@
+// args: -Rcandidates
+
+template<typename T>
+void t0(T t) { // expected-remark {{could be noexcept}}
+ t.~T();
+}
+
+void t1() { // expected-remark {{could be noexcept}}
+ t0(1);
+}
+
+template<typename T>
+void t2(T t) noexcept {
+ t.~T();
+}
+
+void t3() noexcept {
+ t2(1);
+}
diff --git a/tests/r-implicit-noexcept-sys.h b/tests/r-implicit-noexcept-sys.h
new file mode 100644
index 0000000..297dcfc
--- /dev/null
+++ b/tests/r-implicit-noexcept-sys.h
@@ -0,0 +1,25 @@
+#pragma clang system_header
+
+namespace sys {
+
+namespace n0 {
+void t0() noexcept {throw 1;}
+struct S {
+ S() noexcept {throw 1;}
+ ~S() {throw 1;}
+ void f() noexcept {throw 1;}
+ operator int() noexcept {throw 1;}
+};
+
+struct T {
+ T() {throw 1;}
+ ~T() noexcept(false) {throw 1;}
+ void f() {throw 1;}
+ operator int() {throw 1;}
+};
+
+void t1() {}
+void t2() throw();
+}
+
+}
diff --git a/tests/recursion.cpp b/tests/recursion.cpp
new file mode 100644
index 0000000..fb15d5f
--- /dev/null
+++ b/tests/recursion.cpp
@@ -0,0 +1,29 @@
+// args: -Rcandidates
+
+void t0();
+void t1();
+
+void t0() { // expected-remark {{could be noexcept}}
+ t1();
+}
+
+void t1() { // expected-remark {{could be noexcept}}
+ t0();
+}
+
+struct S0 {
+ void f() { // expected-remark {{could be noexcept}}
+ f();
+ }
+
+ S0() {
+ new S0();
+ }
+
+ ~S0() {
+ delete this;
+ }
+};
+
+enum class E { e };
+const E e = e;
diff --git a/tests/redecl.cpp b/tests/redecl.cpp
deleted file mode 100644
index 8b19c5d..0000000
--- a/tests/redecl.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-void x0();
-void x1();
-
-struct S {
- S();
- S(int);
-};
-
-void t0() noexcept // expected-error {{is noexcept}}
-{
- x0(); // expected-note {{is here}}
- x1();
-}
-
-void t1() noexcept // expected-error {{is noexcept}}
-{
- (void) S(); // expected-note {{is here}}
- (void) S(0);
-}
-
-inline void x0() { throw 1; } // expected-note {{is here}}
-inline void x1() { }
-
-S::S() { throw 1; } // expected-note {{is here}}
-S::S(int) { }
diff --git a/tests/run.sh b/tests/run.sh
index 2df0a5a..34929b3 100755
--- a/tests/run.sh
+++ b/tests/run.sh
@@ -12,6 +12,7 @@ args=(
-fsyntax-only
-std=c++14
-fplugin="$PWD/static-noexcept.so"
+ -ferror-limit=0
)
while read line; do
diff --git a/tests/simple-call.cpp b/tests/simple-call.cpp
deleted file mode 100644
index c021255..0000000
--- a/tests/simple-call.cpp
+++ /dev/null
@@ -1,34 +0,0 @@
-extern "C" void c_call();
-
-void foo() noexcept(false);
-
-void t0() noexcept
-{
- c_call();
-}
-
-void t1() noexcept // expected-error {{is noexcept}}
-{
- foo(); // expected-note {{is here}}
-}
-
-void t2() noexcept // expected-error {{is noexcept}}
-{
- (&foo)(); // expected-note {{is here}}
-}
-
-void t3() noexcept // expected-error {{is noexcept}}
-{
- (&c_call)(); // expected-note {{is here}}
-}
-
-template<bool B>
-void dependent() noexcept(B) // expected-note {{declared here}}
-{
- foo(); // expected-note {{is here}}
-}
-
-void templ_call() noexcept // expected-error {{is noexcept}}
-{
- dependent<true>(); // expected-note {{instantiated from here}}
-}
diff --git a/tests/static-initializer-throws.cpp b/tests/static-initializer-throws.cpp
new file mode 100644
index 0000000..80ec6eb
--- /dev/null
+++ b/tests/static-initializer-throws.cpp
@@ -0,0 +1,3 @@
+int foo();
+
+static int x = foo(); // expected-no-diagnostics
diff --git a/tests/system-header.cpp b/tests/system-header.cpp
new file mode 100644
index 0000000..7588cc2
--- /dev/null
+++ b/tests/system-header.cpp
@@ -0,0 +1,3 @@
+// args: -Rcandidates
+
+#include "system-header.h" // expected-no-diagnostics
diff --git a/tests/system-header.h b/tests/system-header.h
new file mode 100644
index 0000000..c4c11f0
--- /dev/null
+++ b/tests/system-header.h
@@ -0,0 +1,16 @@
+#pragma clang system_header
+
+namespace sys {
+void t0() noexcept { throw 1; }
+void t1() {}
+
+struct S0 {
+ ~S0() { throw 1; }
+
+ operator int() noexcept { throw 1; }
+};
+
+struct S1 {
+ ~S1() noexcept(false) {}
+};
+}
diff --git a/tests/throw-stmt.cpp b/tests/throw-stmt.cpp
deleted file mode 100644
index 1f631ae..0000000
--- a/tests/throw-stmt.cpp
+++ /dev/null
@@ -1,20 +0,0 @@
-void free_noexcept() noexcept(true) // expected-error {{is noexcept}}
-{
- throw 1; // expected-note {{is here}}
-}
-
-void free_except() noexcept(false)
-{
- throw 1;
-}
-
-template<bool B>
-void free_dep() noexcept(B) // expected-note {{declared here}}
-{
- throw 1; // expected-note {{is here}}
-}
-
-void t0() noexcept // expected-error {{is noexcept}}
-{
- free_dep<false>(); // expected-note {{instantiated from here}}
-}
diff --git a/tests/throw.cpp b/tests/throw.cpp
new file mode 100644
index 0000000..dfa13ab
--- /dev/null
+++ b/tests/throw.cpp
@@ -0,0 +1,20 @@
+// args: -Rcandidates
+
+void t0() {} // expected-remark {{could be noexcept}}
+void t1() { throw 1; }
+void t2() noexcept {}
+void t3() noexcept { // expected-error {{is noexcept}}
+ throw 1; // expected-note {{is here}}
+}
+
+struct S0 {
+ operator int();
+ operator short() noexcept;
+};
+
+void t4() noexcept { // expected-error {{is noexcept}}
+ (void) (int) S0(); // expected-note {{is here}}
+}
+void t5() { // expected-remark {{could be noexcept}}
+ (void) (short) S0();
+}
diff --git a/tests/try-catch.cpp b/tests/try-catch.cpp
deleted file mode 100644
index 2f41494..0000000
--- a/tests/try-catch.cpp
+++ /dev/null
@@ -1,8 +0,0 @@
-void test() noexcept
-{
- try {
- throw 1;
- } catch (...) {
- // expected-no-diagnostics
- }
-}
diff --git a/tests/typeid.cpp b/tests/typeid.cpp
new file mode 100644
index 0000000..e107b9a
--- /dev/null
+++ b/tests/typeid.cpp
@@ -0,0 +1,23 @@
+// args: -Rcandidates
+
+#include <typeinfo>
+
+struct S { virtual ~S() {} };
+
+void t();
+
+void t0(S* s) { // expected-remark {{could be noexcept}}
+ (void) typeid(s);
+ (void) typeid(!s);
+ (void) typeid(t());
+}
+void t1(S& s) { // expected-remark {{could be noexcept}}
+ (void) typeid(s);
+}
+void t2() { // expected-remark {{could be noexcept}}
+ (void) typeid(S);
+}
+void t3(S* s) noexcept { // expected-error {{is noexcept}}
+ (void) typeid(*s); // expected-note {{is here}}
+ (void) typeid((*s)); // expected-note {{is here}}
+}
diff --git a/tests/violations-in-templates.cpp b/tests/violations-in-templates.cpp
new file mode 100644
index 0000000..fcdeb47
--- /dev/null
+++ b/tests/violations-in-templates.cpp
@@ -0,0 +1,43 @@
+// args: -Rcandidates
+
+template<bool B> void t0() noexcept(B) { // expected-error {{is noexcept}}
+ throw 1; // expected-note {{is here}}
+}
+
+void t1() noexcept {
+ t0<true>(); // expected-note {{instantiated from here}}
+}
+
+struct S0 {
+ S0() = default;
+
+ template<typename T>
+ S0(T) noexcept { // expected-error {{is noexcept}}
+ throw 1; // expected-note {{is here}}
+ }
+
+ template<typename T>
+ operator T() noexcept { // expected-error {{is noexcept}}
+ throw 1; // expected-note {{is here}}
+ }
+};
+
+void t2() noexcept {
+ S0(1); // expected-note {{instantiated from here}}
+ (void) (int) S0(); // expected-note {{instantiated from here}}
+}
+
+template<bool B> void t3() noexcept(B) { // expected-error {{is noexcept}}
+ throw 1; // expected-note {{is here}}
+}
+
+static void (*fn)() = &t3<true>; // expected-note {{instantiated from here}}
+
+struct S1 {
+ template<typename>
+ void f() noexcept { // expected-error {{is noexcept}}
+ throw 1; // expected-note {{is here}}
+ };
+};
+
+static void (S1::*mfn0)() = &S1::f<int>; // expected-note {{instantiated from here}}
diff --git a/tests/wrapped.cpp b/tests/wrapped.cpp
deleted file mode 100644
index 6a99ca9..0000000
--- a/tests/wrapped.cpp
+++ /dev/null
@@ -1,6 +0,0 @@
-void f(int) noexcept;
-int g();
-
-void t0() noexcept { // expected-error {{is noexcept}}
- f(g()); // expected-note {{is here}}
-}