Delete some more files.
This commit is contained in:
parent
7dc062fe0c
commit
e5557c18e5
1924
lib/AST/CFG.cpp
1924
lib/AST/CFG.cpp
File diff suppressed because it is too large
Load Diff
@ -1,322 +0,0 @@
|
|||||||
//===--- ResolveLocation.cpp - Source location resolver ---------*- C++ -*-===//
|
|
||||||
//
|
|
||||||
// The LLVM Compiler Infrastructure
|
|
||||||
//
|
|
||||||
// This file is distributed under the University of Illinois Open Source
|
|
||||||
// License. See LICENSE.TXT for details.
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
//
|
|
||||||
// This defines the ResolveLocationInAST function, which resolves a
|
|
||||||
// source location into a <Decl *, Stmt *> pair.
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
|
|
||||||
#include "clang/Frontend/Utils.h"
|
|
||||||
#include "clang/AST/DeclVisitor.h"
|
|
||||||
#include "clang/AST/StmtVisitor.h"
|
|
||||||
#include "clang/Lex/Lexer.h"
|
|
||||||
#include "clang/Basic/SourceManager.h"
|
|
||||||
#include "llvm/Support/Compiler.h"
|
|
||||||
using namespace clang;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
/// \brief Base for the LocResolver classes. Mostly does source range checking.
|
|
||||||
class VISIBILITY_HIDDEN LocResolverBase {
|
|
||||||
protected:
|
|
||||||
ASTContext &Ctx;
|
|
||||||
SourceLocation Loc;
|
|
||||||
|
|
||||||
Decl *Dcl;
|
|
||||||
Stmt *Stm;
|
|
||||||
bool PassedLoc;
|
|
||||||
|
|
||||||
/// \brief Checks whether Loc is in the source range of 'D'.
|
|
||||||
///
|
|
||||||
/// If it is, updates Dcl. If Loc is passed the source range, it sets
|
|
||||||
/// PassedLoc, otherwise it does nothing.
|
|
||||||
void CheckRange(Decl *D);
|
|
||||||
|
|
||||||
/// \brief Checks whether Loc is in the source range of 'Node'.
|
|
||||||
///
|
|
||||||
/// If it is, updates Stm. If Loc is passed the source range, it sets
|
|
||||||
/// PassedLoc, otherwise it does nothing.
|
|
||||||
void CheckRange(Stmt *Node);
|
|
||||||
|
|
||||||
/// \brief Updates the end source range to cover the full length of the token
|
|
||||||
/// positioned at the end of the source range.
|
|
||||||
///
|
|
||||||
/// e.g.,
|
|
||||||
/// @code
|
|
||||||
/// int foo
|
|
||||||
/// ^ ^
|
|
||||||
/// @endcode
|
|
||||||
/// will be updated to
|
|
||||||
/// @code
|
|
||||||
/// int foo
|
|
||||||
/// ^ ^
|
|
||||||
/// @endcode
|
|
||||||
void FixRange(SourceRange &Range);
|
|
||||||
|
|
||||||
public:
|
|
||||||
LocResolverBase(ASTContext &ctx, SourceLocation loc)
|
|
||||||
: Ctx(ctx), Loc(loc), Dcl(0), Stm(0), PassedLoc(0) {}
|
|
||||||
|
|
||||||
/// \brief We found a AST node that corresponds to the source location.
|
|
||||||
bool FoundIt() const { return Dcl != 0 || Stm != 0; }
|
|
||||||
|
|
||||||
/// \brief We either found a AST node or we passed the source location while
|
|
||||||
/// searching.
|
|
||||||
bool Finished() const { return FoundIt() || PassedLoc; }
|
|
||||||
|
|
||||||
Decl *getDecl() const { return Dcl; }
|
|
||||||
Stmt *getStmt() const { return Stm; }
|
|
||||||
|
|
||||||
std::pair<Decl *, Stmt *> getResult() const {
|
|
||||||
return std::make_pair(getDecl(), getStmt());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// \brief Debugging output.
|
|
||||||
void print(Decl *D);
|
|
||||||
/// \brief Debugging output.
|
|
||||||
void print(Stmt *Node);
|
|
||||||
};
|
|
||||||
|
|
||||||
/// \brief Searches a statement for the AST node that corresponds to a source
|
|
||||||
/// location.
|
|
||||||
class VISIBILITY_HIDDEN StmtLocResolver : public LocResolverBase,
|
|
||||||
public StmtVisitor<StmtLocResolver> {
|
|
||||||
public:
|
|
||||||
StmtLocResolver(ASTContext &ctx, SourceLocation loc)
|
|
||||||
: LocResolverBase(ctx, loc) {}
|
|
||||||
|
|
||||||
void VisitDeclStmt(DeclStmt *Node);
|
|
||||||
void VisitStmt(Stmt *Node);
|
|
||||||
};
|
|
||||||
|
|
||||||
/// \brief Searches a declaration for the AST node that corresponds to a source
|
|
||||||
/// location.
|
|
||||||
class VISIBILITY_HIDDEN DeclLocResolver : public LocResolverBase,
|
|
||||||
public DeclVisitor<DeclLocResolver> {
|
|
||||||
public:
|
|
||||||
DeclLocResolver(ASTContext &ctx, SourceLocation loc)
|
|
||||||
: LocResolverBase(ctx, loc) {}
|
|
||||||
|
|
||||||
void VisitDeclContext(DeclContext *DC);
|
|
||||||
void VisitTranslationUnitDecl(TranslationUnitDecl *TU);
|
|
||||||
void VisitVarDecl(VarDecl *D);
|
|
||||||
void VisitFunctionDecl(FunctionDecl *D);
|
|
||||||
void VisitDecl(Decl *D);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // anonymous namespace
|
|
||||||
|
|
||||||
void StmtLocResolver::VisitDeclStmt(DeclStmt *Node) {
|
|
||||||
CheckRange(Node);
|
|
||||||
if (!FoundIt())
|
|
||||||
return;
|
|
||||||
assert(Stm == Node && "Result not updated ?");
|
|
||||||
|
|
||||||
// Search all declarations of this DeclStmt. If we found the one corresponding
|
|
||||||
// to the source location, update this StmtLocResolver's result.
|
|
||||||
DeclLocResolver DLR(Ctx, Loc);
|
|
||||||
for (DeclStmt::decl_iterator
|
|
||||||
I = Node->decl_begin(), E = Node->decl_end(); I != E; ++I) {
|
|
||||||
DLR.Visit(*I);
|
|
||||||
if (DLR.Finished()) {
|
|
||||||
if (DLR.FoundIt())
|
|
||||||
llvm::tie(Dcl, Stm) = DLR.getResult();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void StmtLocResolver::VisitStmt(Stmt *Node) {
|
|
||||||
CheckRange(Node);
|
|
||||||
if (!FoundIt())
|
|
||||||
return;
|
|
||||||
assert(Stm == Node && "Result not updated ?");
|
|
||||||
|
|
||||||
// Search the child statements.
|
|
||||||
StmtLocResolver SLR(Ctx, Loc);
|
|
||||||
for (Stmt::child_iterator
|
|
||||||
I = Node->child_begin(), E = Node->child_end(); I != E; ++I) {
|
|
||||||
SLR.Visit(*I);
|
|
||||||
if (!SLR.Finished())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// We either found it or we passed the source location.
|
|
||||||
|
|
||||||
if (SLR.FoundIt()) {
|
|
||||||
// Only update Dcl if we found another more immediate 'parent' Decl for
|
|
||||||
// the statement.
|
|
||||||
if (SLR.getDecl())
|
|
||||||
Dcl = SLR.getDecl();
|
|
||||||
Stm = SLR.getStmt();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeclLocResolver::VisitDeclContext(DeclContext *DC) {
|
|
||||||
DeclLocResolver DLR(Ctx, Loc);
|
|
||||||
for (DeclContext::decl_iterator
|
|
||||||
I = DC->decls_begin(), E = DC->decls_end(); I != E; ++I) {
|
|
||||||
DLR.Visit(*I);
|
|
||||||
if (DLR.Finished()) {
|
|
||||||
if (DLR.FoundIt())
|
|
||||||
llvm::tie(Dcl, Stm) = DLR.getResult();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeclLocResolver::VisitTranslationUnitDecl(TranslationUnitDecl *TU) {
|
|
||||||
VisitDeclContext(TU);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeclLocResolver::VisitFunctionDecl(FunctionDecl *D) {
|
|
||||||
CheckRange(D);
|
|
||||||
if (!FoundIt())
|
|
||||||
return;
|
|
||||||
assert(Dcl == D && "Result not updated ?");
|
|
||||||
|
|
||||||
// First, search through the parameters of the function.
|
|
||||||
DeclLocResolver ParmRes(Ctx, Loc);
|
|
||||||
for (FunctionDecl::param_iterator
|
|
||||||
I = D->param_begin(), E = D->param_end(); I != E; ++I) {
|
|
||||||
ParmRes.Visit(*I);
|
|
||||||
if (ParmRes.Finished()) {
|
|
||||||
if (ParmRes.FoundIt())
|
|
||||||
llvm::tie(Dcl, Stm) = ParmRes.getResult();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We didn't found the location in the parameters and we didn't get passed it.
|
|
||||||
|
|
||||||
// Second, search through the declarations that are part of the function.
|
|
||||||
// If we find he location there, we won't have to search through its body.
|
|
||||||
DeclLocResolver DLR(Ctx, Loc);
|
|
||||||
DLR.VisitDeclContext(D);
|
|
||||||
if (DLR.FoundIt()) {
|
|
||||||
llvm::tie(Dcl, Stm) = DLR.getResult();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We didn't find a declaration that corresponds to the source location.
|
|
||||||
|
|
||||||
// Finally, search through the body of the function.
|
|
||||||
if (D->isThisDeclarationADefinition()) {
|
|
||||||
StmtLocResolver SLR(Ctx, Loc);
|
|
||||||
SLR.Visit(D->getBody());
|
|
||||||
if (SLR.FoundIt()) {
|
|
||||||
llvm::tie(Dcl, Stm) = SLR.getResult();
|
|
||||||
// If we didn't find a more immediate 'parent' declaration for the
|
|
||||||
// statement, set the function as the parent.
|
|
||||||
if (Dcl == 0)
|
|
||||||
Dcl = D;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeclLocResolver::VisitVarDecl(VarDecl *D) {
|
|
||||||
CheckRange(D);
|
|
||||||
if (!FoundIt())
|
|
||||||
return;
|
|
||||||
assert(Dcl == D && "Result not updated ?");
|
|
||||||
|
|
||||||
// Check whether the location points to the init expression.
|
|
||||||
if (D->getInit()) {
|
|
||||||
StmtLocResolver SLR(Ctx, Loc);
|
|
||||||
SLR.Visit(D->getInit());
|
|
||||||
Stm = SLR.getStmt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeclLocResolver::VisitDecl(Decl *D) {
|
|
||||||
CheckRange(D);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LocResolverBase::CheckRange(Decl *D) {
|
|
||||||
SourceRange Range = D->getSourceRange();
|
|
||||||
if (!Range.isValid())
|
|
||||||
return;
|
|
||||||
|
|
||||||
FixRange(Range);
|
|
||||||
|
|
||||||
SourceManager &SourceMgr = Ctx.getSourceManager();
|
|
||||||
if (SourceMgr.isBeforeInTranslationUnit(Range.getEnd(), Loc))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (SourceMgr.isBeforeInTranslationUnit(Loc, Range.getBegin()))
|
|
||||||
PassedLoc = true;
|
|
||||||
else
|
|
||||||
Dcl = D;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LocResolverBase::CheckRange(Stmt *Node) {
|
|
||||||
SourceRange Range = Node->getSourceRange();
|
|
||||||
if (!Range.isValid())
|
|
||||||
return;
|
|
||||||
|
|
||||||
FixRange(Range);
|
|
||||||
|
|
||||||
SourceManager &SourceMgr = Ctx.getSourceManager();
|
|
||||||
if (SourceMgr.isBeforeInTranslationUnit(Range.getEnd(), Loc))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (SourceMgr.isBeforeInTranslationUnit(Loc, Range.getBegin()))
|
|
||||||
PassedLoc = true;
|
|
||||||
else
|
|
||||||
Stm = Node;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LocResolverBase::FixRange(SourceRange &Range) {
|
|
||||||
if (!Range.isValid())
|
|
||||||
return;
|
|
||||||
|
|
||||||
unsigned TokSize = Lexer::MeasureTokenLength(Range.getEnd(),
|
|
||||||
Ctx.getSourceManager(),
|
|
||||||
Ctx.getLangOptions());
|
|
||||||
Range.setEnd(Range.getEnd().getFileLocWithOffset(TokSize-1));
|
|
||||||
}
|
|
||||||
|
|
||||||
void LocResolverBase::print(Decl *D) {
|
|
||||||
llvm::raw_ostream &OS = llvm::outs();
|
|
||||||
OS << "#### DECL ####\n";
|
|
||||||
D->print(OS);
|
|
||||||
OS << " <";
|
|
||||||
D->getLocStart().print(OS, Ctx.getSourceManager());
|
|
||||||
OS << " > - <";
|
|
||||||
D->getLocEnd().print(OS, Ctx.getSourceManager());
|
|
||||||
OS << ">\n\n";
|
|
||||||
OS.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
void LocResolverBase::print(Stmt *Node) {
|
|
||||||
llvm::raw_ostream &OS = llvm::outs();
|
|
||||||
OS << "#### STMT ####\n";
|
|
||||||
Node->printPretty(OS, Ctx, 0, PrintingPolicy(Ctx.getLangOptions()));
|
|
||||||
OS << " <";
|
|
||||||
Node->getLocStart().print(OS, Ctx.getSourceManager());
|
|
||||||
OS << " > - <";
|
|
||||||
Node->getLocEnd().print(OS, Ctx.getSourceManager());
|
|
||||||
OS << ">\n\n";
|
|
||||||
OS.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// \brief Returns the AST node that a source location points to.
|
|
||||||
///
|
|
||||||
std::pair<Decl *, Stmt *>
|
|
||||||
clang::ResolveLocationInAST(ASTContext &Ctx, SourceLocation Loc) {
|
|
||||||
if (Loc.isInvalid())
|
|
||||||
return std::make_pair((Decl*)0, (Stmt*)0);
|
|
||||||
|
|
||||||
DeclLocResolver DLR(Ctx, Loc);
|
|
||||||
DLR.Visit(Ctx.getTranslationUnitDecl());
|
|
||||||
return DLR.getResult();
|
|
||||||
}
|
|
@ -1,350 +0,0 @@
|
|||||||
//===---- SemaInherit.cpp - C++ Inheritance ---------------------*- C++ -*-===//
|
|
||||||
//
|
|
||||||
// The LLVM Compiler Infrastructure
|
|
||||||
//
|
|
||||||
// This file is distributed under the University of Illinois Open Source
|
|
||||||
// License. See LICENSE.TXT for details.
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
//
|
|
||||||
// This file provides Sema routines for C++ inheritance semantics,
|
|
||||||
// including searching the inheritance hierarchy.
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
|
|
||||||
#include "SemaInherit.h"
|
|
||||||
#include "Sema.h"
|
|
||||||
#include "clang/AST/ASTContext.h"
|
|
||||||
#include "clang/AST/DeclCXX.h"
|
|
||||||
#include "clang/AST/Type.h"
|
|
||||||
#include "clang/AST/TypeOrdering.h"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <memory>
|
|
||||||
#include <set>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
using namespace clang;
|
|
||||||
|
|
||||||
/// \brief Computes the set of declarations referenced by these base
|
|
||||||
/// paths.
|
|
||||||
void BasePaths::ComputeDeclsFound() {
|
|
||||||
assert(NumDeclsFound == 0 && !DeclsFound &&
|
|
||||||
"Already computed the set of declarations");
|
|
||||||
|
|
||||||
std::set<NamedDecl *> Decls;
|
|
||||||
for (BasePaths::paths_iterator Path = begin(), PathEnd = end();
|
|
||||||
Path != PathEnd; ++Path)
|
|
||||||
Decls.insert(*Path->Decls.first);
|
|
||||||
|
|
||||||
NumDeclsFound = Decls.size();
|
|
||||||
DeclsFound = new NamedDecl * [NumDeclsFound];
|
|
||||||
std::copy(Decls.begin(), Decls.end(), DeclsFound);
|
|
||||||
}
|
|
||||||
|
|
||||||
BasePaths::decl_iterator BasePaths::found_decls_begin() {
|
|
||||||
if (NumDeclsFound == 0)
|
|
||||||
ComputeDeclsFound();
|
|
||||||
return DeclsFound;
|
|
||||||
}
|
|
||||||
|
|
||||||
BasePaths::decl_iterator BasePaths::found_decls_end() {
|
|
||||||
if (NumDeclsFound == 0)
|
|
||||||
ComputeDeclsFound();
|
|
||||||
return DeclsFound + NumDeclsFound;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// isAmbiguous - Determines whether the set of paths provided is
|
|
||||||
/// ambiguous, i.e., there are two or more paths that refer to
|
|
||||||
/// different base class subobjects of the same type. BaseType must be
|
|
||||||
/// an unqualified, canonical class type.
|
|
||||||
bool BasePaths::isAmbiguous(QualType BaseType) {
|
|
||||||
assert(BaseType->isCanonical() && "Base type must be the canonical type");
|
|
||||||
assert(BaseType.getCVRQualifiers() == 0 && "Base type must be unqualified");
|
|
||||||
std::pair<bool, unsigned>& Subobjects = ClassSubobjects[BaseType];
|
|
||||||
return Subobjects.second + (Subobjects.first? 1 : 0) > 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// clear - Clear out all prior path information.
|
|
||||||
void BasePaths::clear() {
|
|
||||||
Paths.clear();
|
|
||||||
ClassSubobjects.clear();
|
|
||||||
ScratchPath.clear();
|
|
||||||
DetectedVirtual = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Swaps the contents of this BasePaths structure with the
|
|
||||||
/// contents of Other.
|
|
||||||
void BasePaths::swap(BasePaths &Other) {
|
|
||||||
std::swap(Origin, Other.Origin);
|
|
||||||
Paths.swap(Other.Paths);
|
|
||||||
ClassSubobjects.swap(Other.ClassSubobjects);
|
|
||||||
std::swap(FindAmbiguities, Other.FindAmbiguities);
|
|
||||||
std::swap(RecordPaths, Other.RecordPaths);
|
|
||||||
std::swap(DetectVirtual, Other.DetectVirtual);
|
|
||||||
std::swap(DetectedVirtual, Other.DetectedVirtual);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// IsDerivedFrom - Determine whether the type Derived is derived from
|
|
||||||
/// the type Base, ignoring qualifiers on Base and Derived. This
|
|
||||||
/// routine does not assess whether an actual conversion from a
|
|
||||||
/// Derived* to a Base* is legal, because it does not account for
|
|
||||||
/// ambiguous conversions or conversions to private/protected bases.
|
|
||||||
bool Sema::IsDerivedFrom(QualType Derived, QualType Base) {
|
|
||||||
BasePaths Paths(/*FindAmbiguities=*/false, /*RecordPaths=*/false,
|
|
||||||
/*DetectVirtual=*/false);
|
|
||||||
return IsDerivedFrom(Derived, Base, Paths);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// IsDerivedFrom - Determine whether the type Derived is derived from
|
|
||||||
/// the type Base, ignoring qualifiers on Base and Derived. This
|
|
||||||
/// routine does not assess whether an actual conversion from a
|
|
||||||
/// Derived* to a Base* is legal, because it does not account for
|
|
||||||
/// ambiguous conversions or conversions to private/protected
|
|
||||||
/// bases. This routine will use Paths to determine if there are
|
|
||||||
/// ambiguous paths (if @c Paths.isFindingAmbiguities()) and record
|
|
||||||
/// information about all of the paths (if @c Paths.isRecordingPaths()).
|
|
||||||
bool Sema::IsDerivedFrom(QualType Derived, QualType Base, BasePaths &Paths) {
|
|
||||||
Derived = Context.getCanonicalType(Derived).getUnqualifiedType();
|
|
||||||
Base = Context.getCanonicalType(Base).getUnqualifiedType();
|
|
||||||
|
|
||||||
if (!Derived->isRecordType() || !Base->isRecordType())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (Derived == Base)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
Paths.setOrigin(Derived);
|
|
||||||
return LookupInBases(cast<CXXRecordDecl>(Derived->getAsRecordType()->getDecl()),
|
|
||||||
MemberLookupCriteria(Base), Paths);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// LookupInBases - Look for something that meets the specified
|
|
||||||
/// Criteria within the base classes of Class (or any of its base
|
|
||||||
/// classes, transitively). This routine populates BasePaths with the
|
|
||||||
/// list of paths that one can take to find the entity that meets the
|
|
||||||
/// search criteria, and returns true if any such entity is found. The
|
|
||||||
/// various options passed to the BasePath constructor will affect the
|
|
||||||
/// behavior of this lookup, e.g., whether it finds ambiguities,
|
|
||||||
/// records paths, or attempts to detect the use of virtual base
|
|
||||||
/// classes.
|
|
||||||
bool Sema::LookupInBases(CXXRecordDecl *Class,
|
|
||||||
const MemberLookupCriteria& Criteria,
|
|
||||||
BasePaths &Paths) {
|
|
||||||
bool FoundPath = false;
|
|
||||||
|
|
||||||
for (CXXRecordDecl::base_class_const_iterator BaseSpec = Class->bases_begin(),
|
|
||||||
BaseSpecEnd = Class->bases_end();
|
|
||||||
BaseSpec != BaseSpecEnd; ++BaseSpec) {
|
|
||||||
// Find the record of the base class subobjects for this type.
|
|
||||||
QualType BaseType = Context.getCanonicalType(BaseSpec->getType());
|
|
||||||
BaseType = BaseType.getUnqualifiedType();
|
|
||||||
|
|
||||||
// If a base class of the class template depends on a template-parameter,
|
|
||||||
// the base class scope is not examined during unqualified name lookup.
|
|
||||||
// [temp.dep]p3.
|
|
||||||
if (BaseType->isDependentType())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Determine whether we need to visit this base class at all,
|
|
||||||
// updating the count of subobjects appropriately.
|
|
||||||
std::pair<bool, unsigned>& Subobjects = Paths.ClassSubobjects[BaseType];
|
|
||||||
bool VisitBase = true;
|
|
||||||
bool SetVirtual = false;
|
|
||||||
if (BaseSpec->isVirtual()) {
|
|
||||||
VisitBase = !Subobjects.first;
|
|
||||||
Subobjects.first = true;
|
|
||||||
if (Paths.isDetectingVirtual() && Paths.DetectedVirtual == 0) {
|
|
||||||
// If this is the first virtual we find, remember it. If it turns out
|
|
||||||
// there is no base path here, we'll reset it later.
|
|
||||||
Paths.DetectedVirtual = BaseType->getAsRecordType();
|
|
||||||
SetVirtual = true;
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
++Subobjects.second;
|
|
||||||
|
|
||||||
if (Paths.isRecordingPaths()) {
|
|
||||||
// Add this base specifier to the current path.
|
|
||||||
BasePathElement Element;
|
|
||||||
Element.Base = &*BaseSpec;
|
|
||||||
Element.Class = Class;
|
|
||||||
if (BaseSpec->isVirtual())
|
|
||||||
Element.SubobjectNumber = 0;
|
|
||||||
else
|
|
||||||
Element.SubobjectNumber = Subobjects.second;
|
|
||||||
Paths.ScratchPath.push_back(Element);
|
|
||||||
}
|
|
||||||
|
|
||||||
CXXRecordDecl *BaseRecord
|
|
||||||
= cast<CXXRecordDecl>(BaseSpec->getType()->getAsRecordType()->getDecl());
|
|
||||||
|
|
||||||
// Either look at the base class type or look into the base class
|
|
||||||
// type to see if we've found a member that meets the search
|
|
||||||
// criteria.
|
|
||||||
bool FoundPathToThisBase = false;
|
|
||||||
switch (Criteria.Kind) {
|
|
||||||
case MemberLookupCriteria::LK_Base:
|
|
||||||
FoundPathToThisBase
|
|
||||||
= (Context.getCanonicalType(BaseSpec->getType()) == Criteria.Base);
|
|
||||||
break;
|
|
||||||
case MemberLookupCriteria::LK_NamedMember:
|
|
||||||
Paths.ScratchPath.Decls = BaseRecord->lookup(Criteria.Name);
|
|
||||||
while (Paths.ScratchPath.Decls.first != Paths.ScratchPath.Decls.second) {
|
|
||||||
if (isAcceptableLookupResult(*Paths.ScratchPath.Decls.first,
|
|
||||||
Criteria.NameKind, Criteria.IDNS)) {
|
|
||||||
FoundPathToThisBase = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++Paths.ScratchPath.Decls.first;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MemberLookupCriteria::LK_OverriddenMember:
|
|
||||||
Paths.ScratchPath.Decls =
|
|
||||||
BaseRecord->lookup(Criteria.Method->getDeclName());
|
|
||||||
while (Paths.ScratchPath.Decls.first != Paths.ScratchPath.Decls.second) {
|
|
||||||
if (CXXMethodDecl *MD =
|
|
||||||
dyn_cast<CXXMethodDecl>(*Paths.ScratchPath.Decls.first)) {
|
|
||||||
OverloadedFunctionDecl::function_iterator MatchedDecl;
|
|
||||||
if (MD->isVirtual() &&
|
|
||||||
!IsOverload(Criteria.Method, MD, MatchedDecl)) {
|
|
||||||
FoundPathToThisBase = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
++Paths.ScratchPath.Decls.first;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (FoundPathToThisBase) {
|
|
||||||
// We've found a path that terminates that this base.
|
|
||||||
FoundPath = true;
|
|
||||||
if (Paths.isRecordingPaths()) {
|
|
||||||
// We have a path. Make a copy of it before moving on.
|
|
||||||
Paths.Paths.push_back(Paths.ScratchPath);
|
|
||||||
} else if (!Paths.isFindingAmbiguities()) {
|
|
||||||
// We found a path and we don't care about ambiguities;
|
|
||||||
// return immediately.
|
|
||||||
return FoundPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// C++ [class.member.lookup]p2:
|
|
||||||
// A member name f in one sub-object B hides a member name f in
|
|
||||||
// a sub-object A if A is a base class sub-object of B. Any
|
|
||||||
// declarations that are so hidden are eliminated from
|
|
||||||
// consideration.
|
|
||||||
else if (VisitBase && LookupInBases(BaseRecord, Criteria, Paths)) {
|
|
||||||
// There is a path to a base class that meets the criteria. If we're not
|
|
||||||
// collecting paths or finding ambiguities, we're done.
|
|
||||||
FoundPath = true;
|
|
||||||
if (!Paths.isFindingAmbiguities())
|
|
||||||
return FoundPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pop this base specifier off the current path (if we're
|
|
||||||
// collecting paths).
|
|
||||||
if (Paths.isRecordingPaths())
|
|
||||||
Paths.ScratchPath.pop_back();
|
|
||||||
// If we set a virtual earlier, and this isn't a path, forget it again.
|
|
||||||
if (SetVirtual && !FoundPath) {
|
|
||||||
Paths.DetectedVirtual = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return FoundPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// CheckDerivedToBaseConversion - Check whether the Derived-to-Base
|
|
||||||
/// conversion (where Derived and Base are class types) is
|
|
||||||
/// well-formed, meaning that the conversion is unambiguous (and
|
|
||||||
/// that all of the base classes are accessible). Returns true
|
|
||||||
/// and emits a diagnostic if the code is ill-formed, returns false
|
|
||||||
/// otherwise. Loc is the location where this routine should point to
|
|
||||||
/// if there is an error, and Range is the source range to highlight
|
|
||||||
/// if there is an error.
|
|
||||||
bool
|
|
||||||
Sema::CheckDerivedToBaseConversion(QualType Derived, QualType Base,
|
|
||||||
unsigned InaccessibleBaseID,
|
|
||||||
unsigned AmbigiousBaseConvID,
|
|
||||||
SourceLocation Loc, SourceRange Range,
|
|
||||||
DeclarationName Name) {
|
|
||||||
// First, determine whether the path from Derived to Base is
|
|
||||||
// ambiguous. This is slightly more expensive than checking whether
|
|
||||||
// the Derived to Base conversion exists, because here we need to
|
|
||||||
// explore multiple paths to determine if there is an ambiguity.
|
|
||||||
BasePaths Paths(/*FindAmbiguities=*/true, /*RecordPaths=*/true,
|
|
||||||
/*DetectVirtual=*/false);
|
|
||||||
bool DerivationOkay = IsDerivedFrom(Derived, Base, Paths);
|
|
||||||
assert(DerivationOkay &&
|
|
||||||
"Can only be used with a derived-to-base conversion");
|
|
||||||
(void)DerivationOkay;
|
|
||||||
|
|
||||||
if (!Paths.isAmbiguous(Context.getCanonicalType(Base).getUnqualifiedType())) {
|
|
||||||
// Check that the base class can be accessed.
|
|
||||||
return CheckBaseClassAccess(Derived, Base, InaccessibleBaseID, Paths, Loc,
|
|
||||||
Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We know that the derived-to-base conversion is ambiguous, and
|
|
||||||
// we're going to produce a diagnostic. Perform the derived-to-base
|
|
||||||
// search just one more time to compute all of the possible paths so
|
|
||||||
// that we can print them out. This is more expensive than any of
|
|
||||||
// the previous derived-to-base checks we've done, but at this point
|
|
||||||
// performance isn't as much of an issue.
|
|
||||||
Paths.clear();
|
|
||||||
Paths.setRecordingPaths(true);
|
|
||||||
bool StillOkay = IsDerivedFrom(Derived, Base, Paths);
|
|
||||||
assert(StillOkay && "Can only be used with a derived-to-base conversion");
|
|
||||||
(void)StillOkay;
|
|
||||||
|
|
||||||
// Build up a textual representation of the ambiguous paths, e.g.,
|
|
||||||
// D -> B -> A, that will be used to illustrate the ambiguous
|
|
||||||
// conversions in the diagnostic. We only print one of the paths
|
|
||||||
// to each base class subobject.
|
|
||||||
std::string PathDisplayStr = getAmbiguousPathsDisplayString(Paths);
|
|
||||||
|
|
||||||
Diag(Loc, AmbigiousBaseConvID)
|
|
||||||
<< Derived << Base << PathDisplayStr << Range << Name;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
Sema::CheckDerivedToBaseConversion(QualType Derived, QualType Base,
|
|
||||||
SourceLocation Loc, SourceRange Range) {
|
|
||||||
return CheckDerivedToBaseConversion(Derived, Base,
|
|
||||||
diag::err_conv_to_inaccessible_base,
|
|
||||||
diag::err_ambiguous_derived_to_base_conv,
|
|
||||||
Loc, Range, DeclarationName());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// @brief Builds a string representing ambiguous paths from a
|
|
||||||
/// specific derived class to different subobjects of the same base
|
|
||||||
/// class.
|
|
||||||
///
|
|
||||||
/// This function builds a string that can be used in error messages
|
|
||||||
/// to show the different paths that one can take through the
|
|
||||||
/// inheritance hierarchy to go from the derived class to different
|
|
||||||
/// subobjects of a base class. The result looks something like this:
|
|
||||||
/// @code
|
|
||||||
/// struct D -> struct B -> struct A
|
|
||||||
/// struct D -> struct C -> struct A
|
|
||||||
/// @endcode
|
|
||||||
std::string Sema::getAmbiguousPathsDisplayString(BasePaths &Paths) {
|
|
||||||
std::string PathDisplayStr;
|
|
||||||
std::set<unsigned> DisplayedPaths;
|
|
||||||
for (BasePaths::paths_iterator Path = Paths.begin();
|
|
||||||
Path != Paths.end(); ++Path) {
|
|
||||||
if (DisplayedPaths.insert(Path->back().SubobjectNumber).second) {
|
|
||||||
// We haven't displayed a path to this particular base
|
|
||||||
// class subobject yet.
|
|
||||||
PathDisplayStr += "\n ";
|
|
||||||
PathDisplayStr += Paths.getOrigin().getAsString();
|
|
||||||
for (BasePath::const_iterator Element = Path->begin();
|
|
||||||
Element != Path->end(); ++Element)
|
|
||||||
PathDisplayStr += " -> " + Element->Base->getType().getAsString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return PathDisplayStr;
|
|
||||||
}
|
|
@ -1,248 +0,0 @@
|
|||||||
//===------ SemaInherit.h - C++ Inheritance ---------------------*- C++ -*-===//
|
|
||||||
//
|
|
||||||
// The LLVM Compiler Infrastructure
|
|
||||||
//
|
|
||||||
// This file is distributed under the University of Illinois Open Source
|
|
||||||
// License. See LICENSE.TXT for details.
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
//
|
|
||||||
// This file provides Sema data structures that help analyse C++
|
|
||||||
// inheritance semantics, including searching the inheritance
|
|
||||||
// hierarchy.
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
|
|
||||||
#ifndef LLVM_CLANG_SEMA_INHERIT_H
|
|
||||||
#define LLVM_CLANG_SEMA_INHERIT_H
|
|
||||||
|
|
||||||
#include "Sema.h"
|
|
||||||
#include "clang/AST/DeclarationName.h"
|
|
||||||
#include "clang/AST/DeclBase.h"
|
|
||||||
#include "clang/AST/Type.h"
|
|
||||||
#include "clang/AST/TypeOrdering.h"
|
|
||||||
#include "llvm/ADT/SmallVector.h"
|
|
||||||
#include <list>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
namespace clang {
|
|
||||||
class CXXBaseSpecifier;
|
|
||||||
|
|
||||||
/// BasePathElement - An element in a path from a derived class to a
|
|
||||||
/// base class. Each step in the path references the link from a
|
|
||||||
/// derived class to one of its direct base classes, along with a
|
|
||||||
/// base "number" that identifies which base subobject of the
|
|
||||||
/// original derived class we are referencing.
|
|
||||||
struct BasePathElement {
|
|
||||||
/// Base - The base specifier that states the link from a derived
|
|
||||||
/// class to a base class, which will be followed by this base
|
|
||||||
/// path element.
|
|
||||||
const CXXBaseSpecifier *Base;
|
|
||||||
|
|
||||||
/// Class - The record decl of the class that the base is a base of.
|
|
||||||
const CXXRecordDecl *Class;
|
|
||||||
|
|
||||||
/// SubobjectNumber - Identifies which base class subobject (of type
|
|
||||||
/// @c Base->getType()) this base path element refers to. This
|
|
||||||
/// value is only valid if @c !Base->isVirtual(), because there
|
|
||||||
/// is no base numbering for the zero or one virtual bases of a
|
|
||||||
/// given type.
|
|
||||||
int SubobjectNumber;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// BasePath - Represents a path from a specific derived class
|
|
||||||
/// (which is not represented as part of the path) to a particular
|
|
||||||
/// (direct or indirect) base class subobject that contains some
|
|
||||||
/// number of declarations with the same name. Individual elements
|
|
||||||
/// in the path are described by the BasePathElement structure,
|
|
||||||
/// which captures both the link from a derived class to one of its
|
|
||||||
/// direct bases and identification describing which base class
|
|
||||||
/// subobject is being used.
|
|
||||||
struct BasePath : public llvm::SmallVector<BasePathElement, 4> {
|
|
||||||
/// Decls - The set of declarations found inside this base class
|
|
||||||
/// subobject.
|
|
||||||
DeclContext::lookup_result Decls;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// BasePaths - Represents the set of paths from a derived class to
|
|
||||||
/// one of its (direct or indirect) bases. For example, given the
|
|
||||||
/// following class hierachy:
|
|
||||||
///
|
|
||||||
/// @code
|
|
||||||
/// class A { };
|
|
||||||
/// class B : public A { };
|
|
||||||
/// class C : public A { };
|
|
||||||
/// class D : public B, public C{ };
|
|
||||||
/// @endcode
|
|
||||||
///
|
|
||||||
/// There are two potential BasePaths to represent paths from D to a
|
|
||||||
/// base subobject of type A. One path is (D,0) -> (B,0) -> (A,0)
|
|
||||||
/// and another is (D,0)->(C,0)->(A,1). These two paths actually
|
|
||||||
/// refer to two different base class subobjects of the same type,
|
|
||||||
/// so the BasePaths object refers to an ambiguous path. On the
|
|
||||||
/// other hand, consider the following class hierarchy:
|
|
||||||
///
|
|
||||||
/// @code
|
|
||||||
/// class A { };
|
|
||||||
/// class B : public virtual A { };
|
|
||||||
/// class C : public virtual A { };
|
|
||||||
/// class D : public B, public C{ };
|
|
||||||
/// @endcode
|
|
||||||
///
|
|
||||||
/// Here, there are two potential BasePaths again, (D, 0) -> (B, 0)
|
|
||||||
/// -> (A,v) and (D, 0) -> (C, 0) -> (A, v), but since both of them
|
|
||||||
/// refer to the same base class subobject of type A (the virtual
|
|
||||||
/// one), there is no ambiguity.
|
|
||||||
class BasePaths {
|
|
||||||
/// Origin - The type from which this search originated.
|
|
||||||
QualType Origin;
|
|
||||||
|
|
||||||
/// Paths - The actual set of paths that can be taken from the
|
|
||||||
/// derived class to the same base class.
|
|
||||||
std::list<BasePath> Paths;
|
|
||||||
|
|
||||||
/// ClassSubobjects - Records the class subobjects for each class
|
|
||||||
/// type that we've seen. The first element in the pair says
|
|
||||||
/// whether we found a path to a virtual base for that class type,
|
|
||||||
/// while the element contains the number of non-virtual base
|
|
||||||
/// class subobjects for that class type. The key of the map is
|
|
||||||
/// the cv-unqualified canonical type of the base class subobject.
|
|
||||||
std::map<QualType, std::pair<bool, unsigned>, QualTypeOrdering>
|
|
||||||
ClassSubobjects;
|
|
||||||
|
|
||||||
/// FindAmbiguities - Whether Sema::IsDerivedFrom should try find
|
|
||||||
/// ambiguous paths while it is looking for a path from a derived
|
|
||||||
/// type to a base type.
|
|
||||||
bool FindAmbiguities;
|
|
||||||
|
|
||||||
/// RecordPaths - Whether Sema::IsDerivedFrom should record paths
|
|
||||||
/// while it is determining whether there are paths from a derived
|
|
||||||
/// type to a base type.
|
|
||||||
bool RecordPaths;
|
|
||||||
|
|
||||||
/// DetectVirtual - Whether Sema::IsDerivedFrom should abort the search
|
|
||||||
/// if it finds a path that goes across a virtual base. The virtual class
|
|
||||||
/// is also recorded.
|
|
||||||
bool DetectVirtual;
|
|
||||||
|
|
||||||
/// ScratchPath - A BasePath that is used by Sema::IsDerivedFrom
|
|
||||||
/// to help build the set of paths.
|
|
||||||
BasePath ScratchPath;
|
|
||||||
|
|
||||||
/// DetectedVirtual - The base class that is virtual.
|
|
||||||
const RecordType *DetectedVirtual;
|
|
||||||
|
|
||||||
/// \brief Array of the declarations that have been found. This
|
|
||||||
/// array is constructed only if needed, e.g., to iterate over the
|
|
||||||
/// results within LookupResult.
|
|
||||||
NamedDecl **DeclsFound;
|
|
||||||
unsigned NumDeclsFound;
|
|
||||||
|
|
||||||
friend class Sema;
|
|
||||||
|
|
||||||
void ComputeDeclsFound();
|
|
||||||
|
|
||||||
public:
|
|
||||||
typedef std::list<BasePath>::const_iterator paths_iterator;
|
|
||||||
typedef NamedDecl **decl_iterator;
|
|
||||||
|
|
||||||
/// BasePaths - Construct a new BasePaths structure to record the
|
|
||||||
/// paths for a derived-to-base search.
|
|
||||||
explicit BasePaths(bool FindAmbiguities = true,
|
|
||||||
bool RecordPaths = true,
|
|
||||||
bool DetectVirtual = true)
|
|
||||||
: FindAmbiguities(FindAmbiguities), RecordPaths(RecordPaths),
|
|
||||||
DetectVirtual(DetectVirtual), DetectedVirtual(0), DeclsFound(0),
|
|
||||||
NumDeclsFound(0)
|
|
||||||
{}
|
|
||||||
|
|
||||||
~BasePaths() { delete [] DeclsFound; }
|
|
||||||
|
|
||||||
paths_iterator begin() const { return Paths.begin(); }
|
|
||||||
paths_iterator end() const { return Paths.end(); }
|
|
||||||
|
|
||||||
BasePath& front() { return Paths.front(); }
|
|
||||||
const BasePath& front() const { return Paths.front(); }
|
|
||||||
|
|
||||||
decl_iterator found_decls_begin();
|
|
||||||
decl_iterator found_decls_end();
|
|
||||||
|
|
||||||
bool isAmbiguous(QualType BaseType);
|
|
||||||
|
|
||||||
/// isFindingAmbiguities - Whether we are finding multiple paths
|
|
||||||
/// to detect ambiguities.
|
|
||||||
bool isFindingAmbiguities() const { return FindAmbiguities; }
|
|
||||||
|
|
||||||
/// isRecordingPaths - Whether we are recording paths.
|
|
||||||
bool isRecordingPaths() const { return RecordPaths; }
|
|
||||||
|
|
||||||
/// setRecordingPaths - Specify whether we should be recording
|
|
||||||
/// paths or not.
|
|
||||||
void setRecordingPaths(bool RP) { RecordPaths = RP; }
|
|
||||||
|
|
||||||
/// isDetectingVirtual - Whether we are detecting virtual bases.
|
|
||||||
bool isDetectingVirtual() const { return DetectVirtual; }
|
|
||||||
|
|
||||||
/// getDetectedVirtual - The virtual base discovered on the path.
|
|
||||||
const RecordType* getDetectedVirtual() const {
|
|
||||||
return DetectedVirtual;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Retrieve the type from which this base-paths search
|
|
||||||
/// began
|
|
||||||
QualType getOrigin() const { return Origin; }
|
|
||||||
void setOrigin(QualType Type) { Origin = Type; }
|
|
||||||
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
void swap(BasePaths &Other);
|
|
||||||
};
|
|
||||||
|
|
||||||
/// MemberLookupCriteria - Criteria for performing lookup of a
|
|
||||||
/// member of a C++ class. Objects of this type are used to direct
|
|
||||||
/// Sema::LookupCXXClassMember.
|
|
||||||
struct MemberLookupCriteria {
|
|
||||||
/// LookupKind - the kind of lookup we're doing.
|
|
||||||
enum LookupKind {
|
|
||||||
LK_Base,
|
|
||||||
LK_NamedMember,
|
|
||||||
LK_OverriddenMember
|
|
||||||
};
|
|
||||||
|
|
||||||
/// MemberLookupCriteria - Constructs member lookup criteria to
|
|
||||||
/// search for a base class of type Base.
|
|
||||||
explicit MemberLookupCriteria(QualType Base)
|
|
||||||
: Kind(LK_Base), Base(Base) { }
|
|
||||||
|
|
||||||
/// MemberLookupCriteria - Constructs member lookup criteria to
|
|
||||||
/// search for a class member with the given Name.
|
|
||||||
explicit MemberLookupCriteria(DeclarationName Name,
|
|
||||||
Sema::LookupNameKind NameKind,
|
|
||||||
unsigned IDNS)
|
|
||||||
: Kind(LK_NamedMember), Name(Name), NameKind(NameKind), IDNS(IDNS) { }
|
|
||||||
|
|
||||||
explicit MemberLookupCriteria(CXXMethodDecl *MD)
|
|
||||||
: Kind(LK_OverriddenMember), Method(MD) { }
|
|
||||||
|
|
||||||
/// Kind - The kind of lookup we're doing.
|
|
||||||
/// LK_Base if we are looking for a base class (whose
|
|
||||||
/// type is Base). LK_NamedMember if we are looking for a named member of
|
|
||||||
/// the class (with the name Name).
|
|
||||||
LookupKind Kind;
|
|
||||||
|
|
||||||
/// Base - The type of the base class we're searching for, if
|
|
||||||
/// LookupBase is true.
|
|
||||||
QualType Base;
|
|
||||||
|
|
||||||
/// Name - The name of the member we're searching for, if
|
|
||||||
/// LookupBase is false.
|
|
||||||
DeclarationName Name;
|
|
||||||
|
|
||||||
Sema::LookupNameKind NameKind;
|
|
||||||
unsigned IDNS;
|
|
||||||
|
|
||||||
CXXMethodDecl *Method;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,932 +0,0 @@
|
|||||||
//===--- SemaNamedCast.cpp - Semantic Analysis for Named Casts ------------===//
|
|
||||||
//
|
|
||||||
// The LLVM Compiler Infrastructure
|
|
||||||
//
|
|
||||||
// This file is distributed under the University of Illinois Open Source
|
|
||||||
// License. See LICENSE.TXT for details.
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
//
|
|
||||||
// This file implements semantic analysis for C++ named casts.
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
|
|
||||||
#include "Sema.h"
|
|
||||||
#include "SemaInherit.h"
|
|
||||||
#include "clang/AST/ExprCXX.h"
|
|
||||||
#include "clang/AST/ASTContext.h"
|
|
||||||
#include "llvm/ADT/SmallVector.h"
|
|
||||||
#include <set>
|
|
||||||
using namespace clang;
|
|
||||||
|
|
||||||
enum TryStaticCastResult {
|
|
||||||
TSC_NotApplicable, ///< The cast method is not applicable.
|
|
||||||
TSC_Success, ///< The cast method is appropriate and successful.
|
|
||||||
TSC_Failed ///< The cast method is appropriate, but failed. A
|
|
||||||
///< diagnostic has been emitted.
|
|
||||||
};
|
|
||||||
|
|
||||||
static void CheckConstCast(Sema &Self, Expr *&SrcExpr, QualType DestType,
|
|
||||||
const SourceRange &OpRange,
|
|
||||||
const SourceRange &DestRange);
|
|
||||||
static void CheckReinterpretCast(Sema &Self, Expr *&SrcExpr, QualType DestType,
|
|
||||||
const SourceRange &OpRange,
|
|
||||||
const SourceRange &DestRange);
|
|
||||||
static void CheckStaticCast(Sema &Self, Expr *&SrcExpr, QualType DestType,
|
|
||||||
const SourceRange &OpRange);
|
|
||||||
static void CheckDynamicCast(Sema &Self, Expr *&SrcExpr, QualType DestType,
|
|
||||||
const SourceRange &OpRange,
|
|
||||||
const SourceRange &DestRange);
|
|
||||||
|
|
||||||
static bool CastsAwayConstness(Sema &Self, QualType SrcType, QualType DestType);
|
|
||||||
static TryStaticCastResult TryLValueToRValueCast(
|
|
||||||
Sema &Self, Expr *SrcExpr, QualType DestType, const SourceRange &OpRange);
|
|
||||||
static TryStaticCastResult TryStaticReferenceDowncast(
|
|
||||||
Sema &Self, Expr *SrcExpr, QualType DestType, const SourceRange &OpRange);
|
|
||||||
static TryStaticCastResult TryStaticPointerDowncast(
|
|
||||||
Sema &Self, QualType SrcType, QualType DestType, const SourceRange &OpRange);
|
|
||||||
static TryStaticCastResult TryStaticMemberPointerUpcast(
|
|
||||||
Sema &Self, QualType SrcType, QualType DestType, const SourceRange &OpRange);
|
|
||||||
static TryStaticCastResult TryStaticDowncast(Sema &Self, QualType SrcType,
|
|
||||||
QualType DestType,
|
|
||||||
const SourceRange &OpRange,
|
|
||||||
QualType OrigSrcType,
|
|
||||||
QualType OrigDestType);
|
|
||||||
static TryStaticCastResult TryStaticImplicitCast(Sema &Self, Expr *SrcExpr,
|
|
||||||
QualType DestType,
|
|
||||||
const SourceRange &OpRange);
|
|
||||||
|
|
||||||
/// ActOnCXXNamedCast - Parse {dynamic,static,reinterpret,const}_cast's.
|
|
||||||
Action::OwningExprResult
|
|
||||||
Sema::ActOnCXXNamedCast(SourceLocation OpLoc, tok::TokenKind Kind,
|
|
||||||
SourceLocation LAngleBracketLoc, TypeTy *Ty,
|
|
||||||
SourceLocation RAngleBracketLoc,
|
|
||||||
SourceLocation LParenLoc, ExprArg E,
|
|
||||||
SourceLocation RParenLoc) {
|
|
||||||
Expr *Ex = E.takeAs<Expr>();
|
|
||||||
QualType DestType = QualType::getFromOpaquePtr(Ty);
|
|
||||||
SourceRange OpRange(OpLoc, RParenLoc);
|
|
||||||
SourceRange DestRange(LAngleBracketLoc, RAngleBracketLoc);
|
|
||||||
|
|
||||||
// If the type is dependent, we won't do the semantic analysis now.
|
|
||||||
// FIXME: should we check this in a more fine-grained manner?
|
|
||||||
bool TypeDependent = DestType->isDependentType() || Ex->isTypeDependent();
|
|
||||||
|
|
||||||
switch (Kind) {
|
|
||||||
default: assert(0 && "Unknown C++ cast!");
|
|
||||||
|
|
||||||
case tok::kw_const_cast:
|
|
||||||
if (!TypeDependent)
|
|
||||||
CheckConstCast(*this, Ex, DestType, OpRange, DestRange);
|
|
||||||
return Owned(new (Context) CXXConstCastExpr(DestType.getNonReferenceType(),
|
|
||||||
Ex, DestType, OpLoc));
|
|
||||||
|
|
||||||
case tok::kw_dynamic_cast:
|
|
||||||
if (!TypeDependent)
|
|
||||||
CheckDynamicCast(*this, Ex, DestType, OpRange, DestRange);
|
|
||||||
return Owned(new (Context)CXXDynamicCastExpr(DestType.getNonReferenceType(),
|
|
||||||
Ex, DestType, OpLoc));
|
|
||||||
|
|
||||||
case tok::kw_reinterpret_cast:
|
|
||||||
if (!TypeDependent)
|
|
||||||
CheckReinterpretCast(*this, Ex, DestType, OpRange, DestRange);
|
|
||||||
return Owned(new (Context) CXXReinterpretCastExpr(
|
|
||||||
DestType.getNonReferenceType(),
|
|
||||||
Ex, DestType, OpLoc));
|
|
||||||
|
|
||||||
case tok::kw_static_cast:
|
|
||||||
if (!TypeDependent)
|
|
||||||
CheckStaticCast(*this, Ex, DestType, OpRange);
|
|
||||||
return Owned(new (Context) CXXStaticCastExpr(DestType.getNonReferenceType(),
|
|
||||||
Ex, DestType, OpLoc));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ExprError();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// CheckConstCast - Check that a const_cast\<DestType\>(SrcExpr) is valid.
|
|
||||||
/// Refer to C++ 5.2.11 for details. const_cast is typically used in code
|
|
||||||
/// like this:
|
|
||||||
/// const char *str = "literal";
|
|
||||||
/// legacy_function(const_cast\<char*\>(str));
|
|
||||||
void
|
|
||||||
CheckConstCast(Sema &Self, Expr *&SrcExpr, QualType DestType,
|
|
||||||
const SourceRange &OpRange, const SourceRange &DestRange)
|
|
||||||
{
|
|
||||||
QualType OrigDestType = DestType, OrigSrcType = SrcExpr->getType();
|
|
||||||
|
|
||||||
DestType = Self.Context.getCanonicalType(DestType);
|
|
||||||
QualType SrcType = SrcExpr->getType();
|
|
||||||
if (const LValueReferenceType *DestTypeTmp =
|
|
||||||
DestType->getAsLValueReferenceType()) {
|
|
||||||
if (SrcExpr->isLvalue(Self.Context) != Expr::LV_Valid) {
|
|
||||||
// Cannot cast non-lvalue to lvalue reference type.
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_cxx_cast_rvalue)
|
|
||||||
<< "const_cast" << OrigDestType << SrcExpr->getSourceRange();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// C++ 5.2.11p4: An lvalue of type T1 can be [cast] to an lvalue of type T2
|
|
||||||
// [...] if a pointer to T1 can be [cast] to the type pointer to T2.
|
|
||||||
DestType = Self.Context.getPointerType(DestTypeTmp->getPointeeType());
|
|
||||||
SrcType = Self.Context.getPointerType(SrcType);
|
|
||||||
} else {
|
|
||||||
// C++ 5.2.11p1: Otherwise, the result is an rvalue and the
|
|
||||||
// lvalue-to-rvalue, array-to-pointer, and function-to-pointer standard
|
|
||||||
// conversions are performed on the expression.
|
|
||||||
Self.DefaultFunctionArrayConversion(SrcExpr);
|
|
||||||
SrcType = SrcExpr->getType();
|
|
||||||
}
|
|
||||||
|
|
||||||
// C++ 5.2.11p5: For a const_cast involving pointers to data members [...]
|
|
||||||
// the rules for const_cast are the same as those used for pointers.
|
|
||||||
|
|
||||||
if (!DestType->isPointerType() && !DestType->isMemberPointerType()) {
|
|
||||||
// Cannot cast to non-pointer, non-reference type. Note that, if DestType
|
|
||||||
// was a reference type, we converted it to a pointer above.
|
|
||||||
// The status of rvalue references isn't entirely clear, but it looks like
|
|
||||||
// conversion to them is simply invalid.
|
|
||||||
// C++ 5.2.11p3: For two pointer types [...]
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_const_cast_dest)
|
|
||||||
<< OrigDestType << DestRange;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (DestType->isFunctionPointerType() ||
|
|
||||||
DestType->isMemberFunctionPointerType()) {
|
|
||||||
// Cannot cast direct function pointers.
|
|
||||||
// C++ 5.2.11p2: [...] where T is any object type or the void type [...]
|
|
||||||
// T is the ultimate pointee of source and target type.
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_const_cast_dest)
|
|
||||||
<< OrigDestType << DestRange;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SrcType = Self.Context.getCanonicalType(SrcType);
|
|
||||||
|
|
||||||
// Unwrap the pointers. Ignore qualifiers. Terminate early if the types are
|
|
||||||
// completely equal.
|
|
||||||
// FIXME: const_cast should probably not be able to convert between pointers
|
|
||||||
// to different address spaces.
|
|
||||||
// C++ 5.2.11p3 describes the core semantics of const_cast. All cv specifiers
|
|
||||||
// in multi-level pointers may change, but the level count must be the same,
|
|
||||||
// as must be the final pointee type.
|
|
||||||
while (SrcType != DestType &&
|
|
||||||
Self.UnwrapSimilarPointerTypes(SrcType, DestType)) {
|
|
||||||
SrcType = SrcType.getUnqualifiedType();
|
|
||||||
DestType = DestType.getUnqualifiedType();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Doug Gregor said to disallow this until users complain.
|
|
||||||
#if 0
|
|
||||||
// If we end up with constant arrays of equal size, unwrap those too. A cast
|
|
||||||
// from const int [N] to int (&)[N] is invalid by my reading of the
|
|
||||||
// standard, but g++ accepts it even with -ansi -pedantic.
|
|
||||||
// No more than one level, though, so don't embed this in the unwrap loop
|
|
||||||
// above.
|
|
||||||
const ConstantArrayType *SrcTypeArr, *DestTypeArr;
|
|
||||||
if ((SrcTypeArr = Self.Context.getAsConstantArrayType(SrcType)) &&
|
|
||||||
(DestTypeArr = Self.Context.getAsConstantArrayType(DestType)))
|
|
||||||
{
|
|
||||||
if (SrcTypeArr->getSize() != DestTypeArr->getSize()) {
|
|
||||||
// Different array sizes.
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_cxx_cast_generic)
|
|
||||||
<< "const_cast" << OrigDestType << OrigSrcType << OpRange;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SrcType = SrcTypeArr->getElementType().getUnqualifiedType();
|
|
||||||
DestType = DestTypeArr->getElementType().getUnqualifiedType();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Since we're dealing in canonical types, the remainder must be the same.
|
|
||||||
if (SrcType != DestType) {
|
|
||||||
// Cast between unrelated types.
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_cxx_cast_generic)
|
|
||||||
<< "const_cast" << OrigDestType << OrigSrcType << OpRange;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// CheckReinterpretCast - Check that a reinterpret_cast\<DestType\>(SrcExpr) is
|
|
||||||
/// valid.
|
|
||||||
/// Refer to C++ 5.2.10 for details. reinterpret_cast is typically used in code
|
|
||||||
/// like this:
|
|
||||||
/// char *bytes = reinterpret_cast\<char*\>(int_ptr);
|
|
||||||
void
|
|
||||||
CheckReinterpretCast(Sema &Self, Expr *&SrcExpr, QualType DestType,
|
|
||||||
const SourceRange &OpRange, const SourceRange &DestRange)
|
|
||||||
{
|
|
||||||
QualType OrigDestType = DestType, OrigSrcType = SrcExpr->getType();
|
|
||||||
|
|
||||||
DestType = Self.Context.getCanonicalType(DestType);
|
|
||||||
QualType SrcType = SrcExpr->getType();
|
|
||||||
if (const LValueReferenceType *DestTypeTmp =
|
|
||||||
DestType->getAsLValueReferenceType()) {
|
|
||||||
if (SrcExpr->isLvalue(Self.Context) != Expr::LV_Valid) {
|
|
||||||
// Cannot cast non-lvalue to reference type.
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_cxx_cast_rvalue)
|
|
||||||
<< "reinterpret_cast" << OrigDestType << SrcExpr->getSourceRange();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// C++ 5.2.10p10: [...] a reference cast reinterpret_cast<T&>(x) has the
|
|
||||||
// same effect as the conversion *reinterpret_cast<T*>(&x) with the
|
|
||||||
// built-in & and * operators.
|
|
||||||
// This code does this transformation for the checked types.
|
|
||||||
DestType = Self.Context.getPointerType(DestTypeTmp->getPointeeType());
|
|
||||||
SrcType = Self.Context.getPointerType(SrcType);
|
|
||||||
} else if (const RValueReferenceType *DestTypeTmp =
|
|
||||||
DestType->getAsRValueReferenceType()) {
|
|
||||||
// Both the reference conversion and the rvalue rules apply.
|
|
||||||
Self.DefaultFunctionArrayConversion(SrcExpr);
|
|
||||||
SrcType = SrcExpr->getType();
|
|
||||||
|
|
||||||
DestType = Self.Context.getPointerType(DestTypeTmp->getPointeeType());
|
|
||||||
SrcType = Self.Context.getPointerType(SrcType);
|
|
||||||
} else {
|
|
||||||
// C++ 5.2.10p1: [...] the lvalue-to-rvalue, array-to-pointer, and
|
|
||||||
// function-to-pointer standard conversions are performed on the
|
|
||||||
// expression v.
|
|
||||||
Self.DefaultFunctionArrayConversion(SrcExpr);
|
|
||||||
SrcType = SrcExpr->getType();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Canonicalize source for comparison.
|
|
||||||
SrcType = Self.Context.getCanonicalType(SrcType);
|
|
||||||
|
|
||||||
const MemberPointerType *DestMemPtr = DestType->getAsMemberPointerType(),
|
|
||||||
*SrcMemPtr = SrcType->getAsMemberPointerType();
|
|
||||||
if (DestMemPtr && SrcMemPtr) {
|
|
||||||
// C++ 5.2.10p9: An rvalue of type "pointer to member of X of type T1"
|
|
||||||
// can be explicitly converted to an rvalue of type "pointer to member
|
|
||||||
// of Y of type T2" if T1 and T2 are both function types or both object
|
|
||||||
// types.
|
|
||||||
if (DestMemPtr->getPointeeType()->isFunctionType() !=
|
|
||||||
SrcMemPtr->getPointeeType()->isFunctionType()) {
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_cxx_cast_generic)
|
|
||||||
<< "reinterpret_cast" << OrigDestType << OrigSrcType << OpRange;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// C++ 5.2.10p2: The reinterpret_cast operator shall not cast away
|
|
||||||
// constness.
|
|
||||||
if (CastsAwayConstness(Self, SrcType, DestType)) {
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_cxx_cast_const_away)
|
|
||||||
<< "reinterpret_cast" << OrigDestType << OrigSrcType << OpRange;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A valid member pointer cast.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// See below for the enumeral issue.
|
|
||||||
if (SrcType->isNullPtrType() && DestType->isIntegralType() &&
|
|
||||||
!DestType->isEnumeralType()) {
|
|
||||||
// C++0x 5.2.10p4: A pointer can be explicitly converted to any integral
|
|
||||||
// type large enough to hold it. A value of std::nullptr_t can be
|
|
||||||
// converted to an integral type; the conversion has the same meaning
|
|
||||||
// and validity as a conversion of (void*)0 to the integral type.
|
|
||||||
if (Self.Context.getTypeSize(SrcType) >
|
|
||||||
Self.Context.getTypeSize(DestType)) {
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_reinterpret_cast_small_int)
|
|
||||||
<< OrigDestType << DestRange;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool destIsPtr = DestType->isPointerType();
|
|
||||||
bool srcIsPtr = SrcType->isPointerType();
|
|
||||||
if (!destIsPtr && !srcIsPtr) {
|
|
||||||
// Except for std::nullptr_t->integer and lvalue->reference, which are
|
|
||||||
// handled above, at least one of the two arguments must be a pointer.
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_cxx_cast_generic)
|
|
||||||
<< "reinterpret_cast" << OrigDestType << OrigSrcType << OpRange;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SrcType == DestType) {
|
|
||||||
// C++ 5.2.10p2 has a note that mentions that, subject to all other
|
|
||||||
// restrictions, a cast to the same type is allowed. The intent is not
|
|
||||||
// entirely clear here, since all other paragraphs explicitly forbid casts
|
|
||||||
// to the same type. However, the behavior of compilers is pretty consistent
|
|
||||||
// on this point: allow same-type conversion if the involved types are
|
|
||||||
// pointers, disallow otherwise.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: Clang treats enumeration types as integral types. If this is ever
|
|
||||||
// changed for C++, the additional check here will be redundant.
|
|
||||||
if (DestType->isIntegralType() && !DestType->isEnumeralType()) {
|
|
||||||
assert(srcIsPtr && "One type must be a pointer");
|
|
||||||
// C++ 5.2.10p4: A pointer can be explicitly converted to any integral
|
|
||||||
// type large enough to hold it.
|
|
||||||
if (Self.Context.getTypeSize(SrcType) >
|
|
||||||
Self.Context.getTypeSize(DestType)) {
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_reinterpret_cast_small_int)
|
|
||||||
<< OrigDestType << DestRange;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SrcType->isIntegralType() || SrcType->isEnumeralType()) {
|
|
||||||
assert(destIsPtr && "One type must be a pointer");
|
|
||||||
// C++ 5.2.10p5: A value of integral or enumeration type can be explicitly
|
|
||||||
// converted to a pointer.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!destIsPtr || !srcIsPtr) {
|
|
||||||
// With the valid non-pointer conversions out of the way, we can be even
|
|
||||||
// more stringent.
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_cxx_cast_generic)
|
|
||||||
<< "reinterpret_cast" << OrigDestType << OrigSrcType << OpRange;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// C++ 5.2.10p2: The reinterpret_cast operator shall not cast away constness.
|
|
||||||
if (CastsAwayConstness(Self, SrcType, DestType)) {
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_cxx_cast_const_away)
|
|
||||||
<< "reinterpret_cast" << OrigDestType << OrigSrcType << OpRange;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not casting away constness, so the only remaining check is for compatible
|
|
||||||
// pointer categories.
|
|
||||||
|
|
||||||
if (SrcType->isFunctionPointerType()) {
|
|
||||||
if (DestType->isFunctionPointerType()) {
|
|
||||||
// C++ 5.2.10p6: A pointer to a function can be explicitly converted to
|
|
||||||
// a pointer to a function of a different type.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// C++0x 5.2.10p8: Converting a pointer to a function into a pointer to
|
|
||||||
// an object type or vice versa is conditionally-supported.
|
|
||||||
// Compilers support it in C++03 too, though, because it's necessary for
|
|
||||||
// casting the return value of dlsym() and GetProcAddress().
|
|
||||||
// FIXME: Conditionally-supported behavior should be configurable in the
|
|
||||||
// TargetInfo or similar.
|
|
||||||
if (!Self.getLangOptions().CPlusPlus0x) {
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::ext_reinterpret_cast_fn_obj)
|
|
||||||
<< OpRange;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DestType->isFunctionPointerType()) {
|
|
||||||
// See above.
|
|
||||||
if (!Self.getLangOptions().CPlusPlus0x) {
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::ext_reinterpret_cast_fn_obj)
|
|
||||||
<< OpRange;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// C++ 5.2.10p7: A pointer to an object can be explicitly converted to
|
|
||||||
// a pointer to an object of different type.
|
|
||||||
// Void pointers are not specified, but supported by every compiler out there.
|
|
||||||
// So we finish by allowing everything that remains - it's got to be two
|
|
||||||
// object pointers.
|
|
||||||
}
|
|
||||||
|
|
||||||
/// CastsAwayConstness - Check if the pointer conversion from SrcType to
|
|
||||||
/// DestType casts away constness as defined in C++ 5.2.11p8ff. This is used by
|
|
||||||
/// the cast checkers. Both arguments must denote pointer (possibly to member)
|
|
||||||
/// types.
|
|
||||||
bool
|
|
||||||
CastsAwayConstness(Sema &Self, QualType SrcType, QualType DestType)
|
|
||||||
{
|
|
||||||
// Casting away constness is defined in C++ 5.2.11p8 with reference to
|
|
||||||
// C++ 4.4. We piggyback on Sema::IsQualificationConversion for this, since
|
|
||||||
// the rules are non-trivial. So first we construct Tcv *...cv* as described
|
|
||||||
// in C++ 5.2.11p8.
|
|
||||||
assert((SrcType->isPointerType() || SrcType->isMemberPointerType()) &&
|
|
||||||
"Source type is not pointer or pointer to member.");
|
|
||||||
assert((DestType->isPointerType() || DestType->isMemberPointerType()) &&
|
|
||||||
"Destination type is not pointer or pointer to member.");
|
|
||||||
|
|
||||||
QualType UnwrappedSrcType = SrcType, UnwrappedDestType = DestType;
|
|
||||||
llvm::SmallVector<unsigned, 8> cv1, cv2;
|
|
||||||
|
|
||||||
// Find the qualifications.
|
|
||||||
while (Self.UnwrapSimilarPointerTypes(UnwrappedSrcType, UnwrappedDestType)) {
|
|
||||||
cv1.push_back(UnwrappedSrcType.getCVRQualifiers());
|
|
||||||
cv2.push_back(UnwrappedDestType.getCVRQualifiers());
|
|
||||||
}
|
|
||||||
assert(cv1.size() > 0 && "Must have at least one pointer level.");
|
|
||||||
|
|
||||||
// Construct void pointers with those qualifiers (in reverse order of
|
|
||||||
// unwrapping, of course).
|
|
||||||
QualType SrcConstruct = Self.Context.VoidTy;
|
|
||||||
QualType DestConstruct = Self.Context.VoidTy;
|
|
||||||
for (llvm::SmallVector<unsigned, 8>::reverse_iterator i1 = cv1.rbegin(),
|
|
||||||
i2 = cv2.rbegin();
|
|
||||||
i1 != cv1.rend(); ++i1, ++i2)
|
|
||||||
{
|
|
||||||
SrcConstruct = Self.Context.getPointerType(
|
|
||||||
SrcConstruct.getQualifiedType(*i1));
|
|
||||||
DestConstruct = Self.Context.getPointerType(
|
|
||||||
DestConstruct.getQualifiedType(*i2));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test if they're compatible.
|
|
||||||
return SrcConstruct != DestConstruct &&
|
|
||||||
!Self.IsQualificationConversion(SrcConstruct, DestConstruct);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// CheckStaticCast - Check that a static_cast\<DestType\>(SrcExpr) is valid.
|
|
||||||
/// Refer to C++ 5.2.9 for details. Static casts are mostly used for making
|
|
||||||
/// implicit conversions explicit and getting rid of data loss warnings.
|
|
||||||
void
|
|
||||||
CheckStaticCast(Sema &Self, Expr *&SrcExpr, QualType DestType,
|
|
||||||
const SourceRange &OpRange)
|
|
||||||
{
|
|
||||||
// The order the tests is not entirely arbitrary. There is one conversion
|
|
||||||
// that can be handled in two different ways. Given:
|
|
||||||
// struct A {};
|
|
||||||
// struct B : public A {
|
|
||||||
// B(); B(const A&);
|
|
||||||
// };
|
|
||||||
// const A &a = B();
|
|
||||||
// the cast static_cast<const B&>(a) could be seen as either a static
|
|
||||||
// reference downcast, or an explicit invocation of the user-defined
|
|
||||||
// conversion using B's conversion constructor.
|
|
||||||
// DR 427 specifies that the downcast is to be applied here.
|
|
||||||
|
|
||||||
// FIXME: With N2812, casts to rvalue refs will change.
|
|
||||||
|
|
||||||
// C++ 5.2.9p4: Any expression can be explicitly converted to type "cv void".
|
|
||||||
if (DestType->isVoidType()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// C++ 5.2.9p5, reference downcast.
|
|
||||||
// See the function for details.
|
|
||||||
// DR 427 specifies that this is to be applied before paragraph 2.
|
|
||||||
if (TryStaticReferenceDowncast(Self, SrcExpr, DestType, OpRange)
|
|
||||||
> TSC_NotApplicable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// N2844 5.2.9p3: An lvalue of type "cv1 T1" can be cast to type "rvalue
|
|
||||||
// reference to cv2 T2" if "cv2 T2" is reference-compatible with "cv1 T1".
|
|
||||||
if (TryLValueToRValueCast(Self, SrcExpr, DestType, OpRange) >
|
|
||||||
TSC_NotApplicable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// C++ 5.2.9p2: An expression e can be explicitly converted to a type T
|
|
||||||
// [...] if the declaration "T t(e);" is well-formed, [...].
|
|
||||||
if (TryStaticImplicitCast(Self, SrcExpr, DestType, OpRange) >
|
|
||||||
TSC_NotApplicable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// C++ 5.2.9p6: May apply the reverse of any standard conversion, except
|
|
||||||
// lvalue-to-rvalue, array-to-pointer, function-to-pointer, and boolean
|
|
||||||
// conversions, subject to further restrictions.
|
|
||||||
// Also, C++ 5.2.9p1 forbids casting away constness, which makes reversal
|
|
||||||
// of qualification conversions impossible.
|
|
||||||
|
|
||||||
// The lvalue-to-rvalue, array-to-pointer and function-to-pointer conversions
|
|
||||||
// are applied to the expression.
|
|
||||||
QualType OrigSrcType = SrcExpr->getType();
|
|
||||||
Self.DefaultFunctionArrayConversion(SrcExpr);
|
|
||||||
|
|
||||||
QualType SrcType = Self.Context.getCanonicalType(SrcExpr->getType());
|
|
||||||
|
|
||||||
// Reverse integral promotion/conversion. All such conversions are themselves
|
|
||||||
// again integral promotions or conversions and are thus already handled by
|
|
||||||
// p2 (TryDirectInitialization above).
|
|
||||||
// (Note: any data loss warnings should be suppressed.)
|
|
||||||
// The exception is the reverse of enum->integer, i.e. integer->enum (and
|
|
||||||
// enum->enum). See also C++ 5.2.9p7.
|
|
||||||
// The same goes for reverse floating point promotion/conversion and
|
|
||||||
// floating-integral conversions. Again, only floating->enum is relevant.
|
|
||||||
if (DestType->isEnumeralType()) {
|
|
||||||
if (SrcType->isComplexType() || SrcType->isVectorType()) {
|
|
||||||
// Fall through - these cannot be converted.
|
|
||||||
} else if (SrcType->isArithmeticType() || SrcType->isEnumeralType()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reverse pointer upcast. C++ 4.10p3 specifies pointer upcast.
|
|
||||||
// C++ 5.2.9p8 additionally disallows a cast path through virtual inheritance.
|
|
||||||
if (TryStaticPointerDowncast(Self, SrcType, DestType, OpRange)
|
|
||||||
> TSC_NotApplicable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reverse member pointer conversion. C++ 4.11 specifies member pointer
|
|
||||||
// conversion. C++ 5.2.9p9 has additional information.
|
|
||||||
// DR54's access restrictions apply here also.
|
|
||||||
if (TryStaticMemberPointerUpcast(Self, SrcType, DestType, OpRange)
|
|
||||||
> TSC_NotApplicable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reverse pointer conversion to void*. C++ 4.10.p2 specifies conversion to
|
|
||||||
// void*. C++ 5.2.9p10 specifies additional restrictions, which really is
|
|
||||||
// just the usual constness stuff.
|
|
||||||
if (const PointerType *SrcPointer = SrcType->getAsPointerType()) {
|
|
||||||
QualType SrcPointee = SrcPointer->getPointeeType();
|
|
||||||
if (SrcPointee->isVoidType()) {
|
|
||||||
if (const PointerType *DestPointer = DestType->getAsPointerType()) {
|
|
||||||
QualType DestPointee = DestPointer->getPointeeType();
|
|
||||||
if (DestPointee->isIncompleteOrObjectType()) {
|
|
||||||
// This is definitely the intended conversion, but it might fail due
|
|
||||||
// to a const violation.
|
|
||||||
if (!DestPointee.isAtLeastAsQualifiedAs(SrcPointee)) {
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_cxx_cast_const_away)
|
|
||||||
<< "static_cast" << DestType << OrigSrcType << OpRange;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We tried everything. Everything! Nothing works! :-(
|
|
||||||
// FIXME: Error reporting could be a lot better. Should store the reason why
|
|
||||||
// every substep failed and, at the end, select the most specific and report
|
|
||||||
// that.
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_cxx_cast_generic)
|
|
||||||
<< "static_cast" << DestType << OrigSrcType
|
|
||||||
<< OpRange;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tests whether a conversion according to N2844 is valid.
|
|
||||||
TryStaticCastResult
|
|
||||||
TryLValueToRValueCast(Sema &Self, Expr *SrcExpr, QualType DestType,
|
|
||||||
const SourceRange &OpRange)
|
|
||||||
{
|
|
||||||
// N2844 5.2.9p3: An lvalue of type "cv1 T1" can be cast to type "rvalue
|
|
||||||
// reference to cv2 T2" if "cv2 T2" is reference-compatible with "cv1 T1".
|
|
||||||
const RValueReferenceType *R = DestType->getAsRValueReferenceType();
|
|
||||||
if (!R)
|
|
||||||
return TSC_NotApplicable;
|
|
||||||
|
|
||||||
if (SrcExpr->isLvalue(Self.Context) != Expr::LV_Valid)
|
|
||||||
return TSC_NotApplicable;
|
|
||||||
|
|
||||||
// Because we try the reference downcast before this function, from now on
|
|
||||||
// this is the only cast possibility, so we issue an error if we fail now.
|
|
||||||
bool DerivedToBase;
|
|
||||||
if (Self.CompareReferenceRelationship(SrcExpr->getType(), R->getPointeeType(),
|
|
||||||
DerivedToBase) <
|
|
||||||
Sema::Ref_Compatible_With_Added_Qualification) {
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_lvalue_to_rvalue_cast)
|
|
||||||
<< SrcExpr->getType() << R->getPointeeType() << OpRange;
|
|
||||||
return TSC_Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Similar to CheckReferenceInit, we actually need more AST annotation
|
|
||||||
// than nothing.
|
|
||||||
return TSC_Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tests whether a conversion according to C++ 5.2.9p5 is valid.
|
|
||||||
TryStaticCastResult
|
|
||||||
TryStaticReferenceDowncast(Sema &Self, Expr *SrcExpr, QualType DestType,
|
|
||||||
const SourceRange &OpRange)
|
|
||||||
{
|
|
||||||
// C++ 5.2.9p5: An lvalue of type "cv1 B", where B is a class type, can be
|
|
||||||
// cast to type "reference to cv2 D", where D is a class derived from B,
|
|
||||||
// if a valid standard conversion from "pointer to D" to "pointer to B"
|
|
||||||
// exists, cv2 >= cv1, and B is not a virtual base class of D.
|
|
||||||
// In addition, DR54 clarifies that the base must be accessible in the
|
|
||||||
// current context. Although the wording of DR54 only applies to the pointer
|
|
||||||
// variant of this rule, the intent is clearly for it to apply to the this
|
|
||||||
// conversion as well.
|
|
||||||
|
|
||||||
if (SrcExpr->isLvalue(Self.Context) != Expr::LV_Valid) {
|
|
||||||
return TSC_NotApplicable;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ReferenceType *DestReference = DestType->getAsReferenceType();
|
|
||||||
if (!DestReference) {
|
|
||||||
return TSC_NotApplicable;
|
|
||||||
}
|
|
||||||
QualType DestPointee = DestReference->getPointeeType();
|
|
||||||
|
|
||||||
return TryStaticDowncast(Self, SrcExpr->getType(), DestPointee, OpRange,
|
|
||||||
SrcExpr->getType(), DestType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tests whether a conversion according to C++ 5.2.9p8 is valid.
|
|
||||||
TryStaticCastResult
|
|
||||||
TryStaticPointerDowncast(Sema &Self, QualType SrcType, QualType DestType,
|
|
||||||
const SourceRange &OpRange)
|
|
||||||
{
|
|
||||||
// C++ 5.2.9p8: An rvalue of type "pointer to cv1 B", where B is a class
|
|
||||||
// type, can be converted to an rvalue of type "pointer to cv2 D", where D
|
|
||||||
// is a class derived from B, if a valid standard conversion from "pointer
|
|
||||||
// to D" to "pointer to B" exists, cv2 >= cv1, and B is not a virtual base
|
|
||||||
// class of D.
|
|
||||||
// In addition, DR54 clarifies that the base must be accessible in the
|
|
||||||
// current context.
|
|
||||||
|
|
||||||
const PointerType *SrcPointer = SrcType->getAsPointerType();
|
|
||||||
if (!SrcPointer) {
|
|
||||||
return TSC_NotApplicable;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PointerType *DestPointer = DestType->getAsPointerType();
|
|
||||||
if (!DestPointer) {
|
|
||||||
return TSC_NotApplicable;
|
|
||||||
}
|
|
||||||
|
|
||||||
return TryStaticDowncast(Self, SrcPointer->getPointeeType(),
|
|
||||||
DestPointer->getPointeeType(),
|
|
||||||
OpRange, SrcType, DestType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// TryStaticDowncast - Common functionality of TryStaticReferenceDowncast and
|
|
||||||
/// TryStaticPointerDowncast. Tests whether a static downcast from SrcType to
|
|
||||||
/// DestType, both of which must be canonical, is possible and allowed.
|
|
||||||
TryStaticCastResult
|
|
||||||
TryStaticDowncast(Sema &Self, QualType SrcType, QualType DestType,
|
|
||||||
const SourceRange &OpRange, QualType OrigSrcType,
|
|
||||||
QualType OrigDestType)
|
|
||||||
{
|
|
||||||
// Downcast can only happen in class hierarchies, so we need classes.
|
|
||||||
if (!DestType->isRecordType() || !SrcType->isRecordType()) {
|
|
||||||
return TSC_NotApplicable;
|
|
||||||
}
|
|
||||||
|
|
||||||
BasePaths Paths(/*FindAmbiguities=*/true, /*RecordPaths=*/false,
|
|
||||||
/*DetectVirtual=*/true);
|
|
||||||
if (!Self.IsDerivedFrom(DestType, SrcType, Paths)) {
|
|
||||||
return TSC_NotApplicable;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Target type does derive from source type. Now we're serious. If an error
|
|
||||||
// appears now, it's not ignored.
|
|
||||||
// This may not be entirely in line with the standard. Take for example:
|
|
||||||
// struct A {};
|
|
||||||
// struct B : virtual A {
|
|
||||||
// B(A&);
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// void f()
|
|
||||||
// {
|
|
||||||
// (void)static_cast<const B&>(*((A*)0));
|
|
||||||
// }
|
|
||||||
// As far as the standard is concerned, p5 does not apply (A is virtual), so
|
|
||||||
// p2 should be used instead - "const B& t(*((A*)0));" is perfectly valid.
|
|
||||||
// However, both GCC and Comeau reject this example, and accepting it would
|
|
||||||
// mean more complex code if we're to preserve the nice error message.
|
|
||||||
// FIXME: Being 100% compliant here would be nice to have.
|
|
||||||
|
|
||||||
// Must preserve cv, as always.
|
|
||||||
if (!DestType.isAtLeastAsQualifiedAs(SrcType)) {
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_cxx_cast_const_away)
|
|
||||||
<< "static_cast" << OrigDestType << OrigSrcType << OpRange;
|
|
||||||
return TSC_Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Paths.isAmbiguous(SrcType.getUnqualifiedType())) {
|
|
||||||
// This code is analoguous to that in CheckDerivedToBaseConversion, except
|
|
||||||
// that it builds the paths in reverse order.
|
|
||||||
// To sum up: record all paths to the base and build a nice string from
|
|
||||||
// them. Use it to spice up the error message.
|
|
||||||
Paths.clear();
|
|
||||||
Paths.setRecordingPaths(true);
|
|
||||||
Self.IsDerivedFrom(DestType, SrcType, Paths);
|
|
||||||
std::string PathDisplayStr;
|
|
||||||
std::set<unsigned> DisplayedPaths;
|
|
||||||
for (BasePaths::paths_iterator Path = Paths.begin();
|
|
||||||
Path != Paths.end(); ++Path) {
|
|
||||||
if (DisplayedPaths.insert(Path->back().SubobjectNumber).second) {
|
|
||||||
// We haven't displayed a path to this particular base
|
|
||||||
// class subobject yet.
|
|
||||||
PathDisplayStr += "\n ";
|
|
||||||
for (BasePath::const_reverse_iterator Element = Path->rbegin();
|
|
||||||
Element != Path->rend(); ++Element)
|
|
||||||
PathDisplayStr += Element->Base->getType().getAsString() + " -> ";
|
|
||||||
PathDisplayStr += DestType.getAsString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_ambiguous_base_to_derived_cast)
|
|
||||||
<< SrcType.getUnqualifiedType() << DestType.getUnqualifiedType()
|
|
||||||
<< PathDisplayStr << OpRange;
|
|
||||||
return TSC_Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Paths.getDetectedVirtual() != 0) {
|
|
||||||
QualType VirtualBase(Paths.getDetectedVirtual(), 0);
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_static_downcast_via_virtual)
|
|
||||||
<< OrigSrcType << OrigDestType << VirtualBase << OpRange;
|
|
||||||
return TSC_Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Test accessibility.
|
|
||||||
|
|
||||||
return TSC_Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// TryStaticMemberPointerUpcast - Tests whether a conversion according to
|
|
||||||
/// C++ 5.2.9p9 is valid:
|
|
||||||
///
|
|
||||||
/// An rvalue of type "pointer to member of D of type cv1 T" can be
|
|
||||||
/// converted to an rvalue of type "pointer to member of B of type cv2 T",
|
|
||||||
/// where B is a base class of D [...].
|
|
||||||
///
|
|
||||||
TryStaticCastResult
|
|
||||||
TryStaticMemberPointerUpcast(Sema &Self, QualType SrcType, QualType DestType,
|
|
||||||
const SourceRange &OpRange)
|
|
||||||
{
|
|
||||||
const MemberPointerType *SrcMemPtr = SrcType->getAsMemberPointerType();
|
|
||||||
if (!SrcMemPtr)
|
|
||||||
return TSC_NotApplicable;
|
|
||||||
const MemberPointerType *DestMemPtr = DestType->getAsMemberPointerType();
|
|
||||||
if (!DestMemPtr)
|
|
||||||
return TSC_NotApplicable;
|
|
||||||
|
|
||||||
// T == T, modulo cv
|
|
||||||
if (Self.Context.getCanonicalType(
|
|
||||||
SrcMemPtr->getPointeeType().getUnqualifiedType()) !=
|
|
||||||
Self.Context.getCanonicalType(DestMemPtr->getPointeeType().
|
|
||||||
getUnqualifiedType()))
|
|
||||||
return TSC_NotApplicable;
|
|
||||||
|
|
||||||
// B base of D
|
|
||||||
QualType SrcClass(SrcMemPtr->getClass(), 0);
|
|
||||||
QualType DestClass(DestMemPtr->getClass(), 0);
|
|
||||||
BasePaths Paths(/*FindAmbiguities=*/true, /*RecordPaths=*/false,
|
|
||||||
/*DetectVirtual=*/true);
|
|
||||||
if (!Self.IsDerivedFrom(SrcClass, DestClass, Paths)) {
|
|
||||||
return TSC_NotApplicable;
|
|
||||||
}
|
|
||||||
|
|
||||||
// B is a base of D. But is it an allowed base? If not, it's a hard error.
|
|
||||||
if (Paths.isAmbiguous(DestClass)) {
|
|
||||||
Paths.clear();
|
|
||||||
Paths.setRecordingPaths(true);
|
|
||||||
bool StillOkay = Self.IsDerivedFrom(SrcClass, DestClass, Paths);
|
|
||||||
assert(StillOkay);
|
|
||||||
StillOkay = StillOkay;
|
|
||||||
std::string PathDisplayStr = Self.getAmbiguousPathsDisplayString(Paths);
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_ambiguous_memptr_conv)
|
|
||||||
<< 1 << SrcClass << DestClass << PathDisplayStr << OpRange;
|
|
||||||
return TSC_Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (const RecordType *VBase = Paths.getDetectedVirtual()) {
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_memptr_conv_via_virtual)
|
|
||||||
<< SrcClass << DestClass << QualType(VBase, 0) << OpRange;
|
|
||||||
return TSC_Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Test accessibility.
|
|
||||||
|
|
||||||
return TSC_Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// TryStaticImplicitCast - Tests whether a conversion according to C++ 5.2.9p2
|
|
||||||
/// is valid:
|
|
||||||
///
|
|
||||||
/// An expression e can be explicitly converted to a type T using a
|
|
||||||
/// @c static_cast if the declaration "T t(e);" is well-formed [...].
|
|
||||||
TryStaticCastResult
|
|
||||||
TryStaticImplicitCast(Sema &Self, Expr *SrcExpr, QualType DestType,
|
|
||||||
const SourceRange &OpRange)
|
|
||||||
{
|
|
||||||
if (DestType->isReferenceType()) {
|
|
||||||
// At this point of CheckStaticCast, if the destination is a reference,
|
|
||||||
// this has to work. There is no other way that works.
|
|
||||||
return Self.CheckReferenceInit(SrcExpr, DestType) ?
|
|
||||||
TSC_Failed : TSC_Success;
|
|
||||||
}
|
|
||||||
if (DestType->isRecordType()) {
|
|
||||||
// FIXME: Use an implementation of C++ [over.match.ctor] for this.
|
|
||||||
return TSC_NotApplicable;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: To get a proper error from invalid conversions here, we need to
|
|
||||||
// reimplement more of this.
|
|
||||||
ImplicitConversionSequence ICS = Self.TryImplicitConversion(
|
|
||||||
SrcExpr, DestType);
|
|
||||||
return ICS.ConversionKind == ImplicitConversionSequence::BadConversion ?
|
|
||||||
TSC_NotApplicable : TSC_Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// CheckDynamicCast - Check that a dynamic_cast\<DestType\>(SrcExpr) is valid.
|
|
||||||
/// Refer to C++ 5.2.7 for details. Dynamic casts are used mostly for runtime-
|
|
||||||
/// checked downcasts in class hierarchies.
|
|
||||||
void
|
|
||||||
CheckDynamicCast(Sema &Self, Expr *&SrcExpr, QualType DestType,
|
|
||||||
const SourceRange &OpRange,
|
|
||||||
const SourceRange &DestRange)
|
|
||||||
{
|
|
||||||
QualType OrigDestType = DestType, OrigSrcType = SrcExpr->getType();
|
|
||||||
DestType = Self.Context.getCanonicalType(DestType);
|
|
||||||
|
|
||||||
// C++ 5.2.7p1: T shall be a pointer or reference to a complete class type,
|
|
||||||
// or "pointer to cv void".
|
|
||||||
|
|
||||||
QualType DestPointee;
|
|
||||||
const PointerType *DestPointer = DestType->getAsPointerType();
|
|
||||||
const ReferenceType *DestReference = DestType->getAsReferenceType();
|
|
||||||
if (DestPointer) {
|
|
||||||
DestPointee = DestPointer->getPointeeType();
|
|
||||||
} else if (DestReference) {
|
|
||||||
DestPointee = DestReference->getPointeeType();
|
|
||||||
} else {
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_dynamic_cast_not_ref_or_ptr)
|
|
||||||
<< OrigDestType << DestRange;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RecordType *DestRecord = DestPointee->getAsRecordType();
|
|
||||||
if (DestPointee->isVoidType()) {
|
|
||||||
assert(DestPointer && "Reference to void is not possible");
|
|
||||||
} else if (DestRecord) {
|
|
||||||
if (Self.RequireCompleteType(OpRange.getBegin(), DestPointee,
|
|
||||||
diag::err_bad_dynamic_cast_incomplete,
|
|
||||||
DestRange))
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_dynamic_cast_not_class)
|
|
||||||
<< DestPointee.getUnqualifiedType() << DestRange;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// C++0x 5.2.7p2: If T is a pointer type, v shall be an rvalue of a pointer to
|
|
||||||
// complete class type, [...]. If T is an lvalue reference type, v shall be
|
|
||||||
// an lvalue of a complete class type, [...]. If T is an rvalue reference
|
|
||||||
// type, v shall be an expression having a complete effective class type,
|
|
||||||
// [...]
|
|
||||||
|
|
||||||
QualType SrcType = Self.Context.getCanonicalType(OrigSrcType);
|
|
||||||
QualType SrcPointee;
|
|
||||||
if (DestPointer) {
|
|
||||||
if (const PointerType *SrcPointer = SrcType->getAsPointerType()) {
|
|
||||||
SrcPointee = SrcPointer->getPointeeType();
|
|
||||||
} else {
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_dynamic_cast_not_ptr)
|
|
||||||
<< OrigSrcType << SrcExpr->getSourceRange();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (DestReference->isLValueReferenceType()) {
|
|
||||||
if (SrcExpr->isLvalue(Self.Context) != Expr::LV_Valid) {
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_cxx_cast_rvalue)
|
|
||||||
<< "dynamic_cast" << OrigDestType << OpRange;
|
|
||||||
}
|
|
||||||
SrcPointee = SrcType;
|
|
||||||
} else {
|
|
||||||
SrcPointee = SrcType;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RecordType *SrcRecord = SrcPointee->getAsRecordType();
|
|
||||||
if (SrcRecord) {
|
|
||||||
if (Self.RequireCompleteType(OpRange.getBegin(), SrcPointee,
|
|
||||||
diag::err_bad_dynamic_cast_incomplete,
|
|
||||||
SrcExpr->getSourceRange()))
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_dynamic_cast_not_class)
|
|
||||||
<< SrcPointee.getUnqualifiedType() << SrcExpr->getSourceRange();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert((DestPointer || DestReference) &&
|
|
||||||
"Bad destination non-ptr/ref slipped through.");
|
|
||||||
assert((DestRecord || DestPointee->isVoidType()) &&
|
|
||||||
"Bad destination pointee slipped through.");
|
|
||||||
assert(SrcRecord && "Bad source pointee slipped through.");
|
|
||||||
|
|
||||||
// C++ 5.2.7p1: The dynamic_cast operator shall not cast away constness.
|
|
||||||
if (!DestPointee.isAtLeastAsQualifiedAs(SrcPointee)) {
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_cxx_cast_const_away)
|
|
||||||
<< "dynamic_cast" << OrigDestType << OrigSrcType << OpRange;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// C++ 5.2.7p3: If the type of v is the same as the required result type,
|
|
||||||
// [except for cv].
|
|
||||||
if (DestRecord == SrcRecord) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// C++ 5.2.7p5
|
|
||||||
// Upcasts are resolved statically.
|
|
||||||
if (DestRecord && Self.IsDerivedFrom(SrcPointee, DestPointee)) {
|
|
||||||
Self.CheckDerivedToBaseConversion(SrcPointee, DestPointee,
|
|
||||||
OpRange.getBegin(), OpRange);
|
|
||||||
// Diagnostic already emitted on error.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// C++ 5.2.7p6: Otherwise, v shall be [polymorphic].
|
|
||||||
const RecordDecl *SrcDecl = SrcRecord->getDecl()->getDefinition(Self.Context);
|
|
||||||
assert(SrcDecl && "Definition missing");
|
|
||||||
if (!cast<CXXRecordDecl>(SrcDecl)->isPolymorphic()) {
|
|
||||||
Self.Diag(OpRange.getBegin(), diag::err_bad_dynamic_cast_not_polymorphic)
|
|
||||||
<< SrcPointee.getUnqualifiedType() << SrcExpr->getSourceRange();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done. Everything else is run-time checks.
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,450 +0,0 @@
|
|||||||
//===--- SemaTemplateInstantiateStmt.cpp - C++ Template Stmt Instantiation ===/
|
|
||||||
//
|
|
||||||
// The LLVM Compiler Infrastructure
|
|
||||||
//
|
|
||||||
// This file is distributed under the University of Illinois Open Source
|
|
||||||
// License. See LICENSE.TXT for details.
|
|
||||||
//===----------------------------------------------------------------------===/
|
|
||||||
//
|
|
||||||
// This file implements C++ template instantiation for statements.
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===/
|
|
||||||
#include "Sema.h"
|
|
||||||
#include "clang/AST/ASTContext.h"
|
|
||||||
#include "clang/AST/DeclTemplate.h"
|
|
||||||
#include "clang/AST/StmtVisitor.h"
|
|
||||||
#include "clang/AST/Expr.h"
|
|
||||||
#include "clang/AST/ExprCXX.h"
|
|
||||||
#include "clang/Parse/DeclSpec.h"
|
|
||||||
#include "llvm/Support/Compiler.h"
|
|
||||||
using namespace clang;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
class VISIBILITY_HIDDEN TemplateStmtInstantiator
|
|
||||||
: public StmtVisitor<TemplateStmtInstantiator, Sema::OwningStmtResult> {
|
|
||||||
Sema &SemaRef;
|
|
||||||
const TemplateArgumentList &TemplateArgs;
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
Sema::FullExprArg FullExpr(T &expr) {
|
|
||||||
return SemaRef.FullExpr(expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
typedef Sema::OwningExprResult OwningExprResult;
|
|
||||||
typedef Sema::OwningStmtResult OwningStmtResult;
|
|
||||||
|
|
||||||
TemplateStmtInstantiator(Sema &SemaRef,
|
|
||||||
const TemplateArgumentList &TemplateArgs)
|
|
||||||
: SemaRef(SemaRef), TemplateArgs(TemplateArgs) { }
|
|
||||||
|
|
||||||
// Declare VisitXXXStmt nodes for all of the statement kinds.
|
|
||||||
#define STMT(Type, Base) OwningStmtResult Visit##Type(Type *S);
|
|
||||||
#define EXPR(Type, Base)
|
|
||||||
#include "clang/AST/StmtNodes.def"
|
|
||||||
|
|
||||||
// Visit an expression (which will use the expression
|
|
||||||
// instantiator).
|
|
||||||
OwningStmtResult VisitExpr(Expr *E);
|
|
||||||
|
|
||||||
// Base case. I'm supposed to ignore this.
|
|
||||||
OwningStmtResult VisitStmt(Stmt *S) {
|
|
||||||
S->dump();
|
|
||||||
assert(false && "Cannot instantiate this kind of statement");
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//===----------------------------------------------------------------------===/
|
|
||||||
// Common/C statements
|
|
||||||
//===----------------------------------------------------------------------===/
|
|
||||||
Sema::OwningStmtResult TemplateStmtInstantiator::VisitDeclStmt(DeclStmt *S) {
|
|
||||||
llvm::SmallVector<Decl *, 4> Decls;
|
|
||||||
for (DeclStmt::decl_iterator D = S->decl_begin(), DEnd = S->decl_end();
|
|
||||||
D != DEnd; ++D) {
|
|
||||||
Decl *Instantiated = SemaRef.InstantiateDecl(*D, SemaRef.CurContext,
|
|
||||||
TemplateArgs);
|
|
||||||
if (!Instantiated)
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
Decls.push_back(Instantiated);
|
|
||||||
SemaRef.CurrentInstantiationScope->InstantiatedLocal(*D, Instantiated);
|
|
||||||
}
|
|
||||||
|
|
||||||
return SemaRef.Owned(new (SemaRef.Context) DeclStmt(
|
|
||||||
DeclGroupRef::Create(SemaRef.Context,
|
|
||||||
&Decls[0],
|
|
||||||
Decls.size()),
|
|
||||||
S->getStartLoc(),
|
|
||||||
S->getEndLoc()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult TemplateStmtInstantiator::VisitNullStmt(NullStmt *S) {
|
|
||||||
return SemaRef.Owned(S->Clone(SemaRef.Context));
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult TemplateStmtInstantiator::VisitLabelStmt(LabelStmt *S) {
|
|
||||||
OwningStmtResult SubStmt = Visit(S->getSubStmt());
|
|
||||||
|
|
||||||
if (SubStmt.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
// FIXME: Pass the real colon loc in.
|
|
||||||
return SemaRef.ActOnLabelStmt(S->getIdentLoc(), S->getID(), SourceLocation(),
|
|
||||||
move(SubStmt));
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult TemplateStmtInstantiator::VisitGotoStmt(GotoStmt *S) {
|
|
||||||
return SemaRef.ActOnGotoStmt(S->getGotoLoc(), S->getLabelLoc(),
|
|
||||||
S->getLabel()->getID());
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult
|
|
||||||
TemplateStmtInstantiator::VisitIndirectGotoStmt(IndirectGotoStmt *S) {
|
|
||||||
OwningExprResult Target = SemaRef.InstantiateExpr(S->getTarget(),
|
|
||||||
TemplateArgs);
|
|
||||||
if (Target.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
return SemaRef.ActOnIndirectGotoStmt(S->getGotoLoc(), S->getStarLoc(),
|
|
||||||
move(Target));
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult TemplateStmtInstantiator::VisitBreakStmt(BreakStmt *S) {
|
|
||||||
return SemaRef.Owned(S->Clone(SemaRef.Context));
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult
|
|
||||||
TemplateStmtInstantiator::VisitContinueStmt(ContinueStmt *S) {
|
|
||||||
return SemaRef.Owned(S->Clone(SemaRef.Context));
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult
|
|
||||||
TemplateStmtInstantiator::VisitReturnStmt(ReturnStmt *S) {
|
|
||||||
Sema::OwningExprResult Result = SemaRef.ExprEmpty();
|
|
||||||
if (Expr *E = S->getRetValue()) {
|
|
||||||
Result = SemaRef.InstantiateExpr(E, TemplateArgs);
|
|
||||||
|
|
||||||
if (Result.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
}
|
|
||||||
|
|
||||||
return SemaRef.ActOnReturnStmt(S->getReturnLoc(), FullExpr(Result));
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult
|
|
||||||
TemplateStmtInstantiator::VisitCompoundStmt(CompoundStmt *S) {
|
|
||||||
return SemaRef.InstantiateCompoundStmt(S, TemplateArgs, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult
|
|
||||||
TemplateStmtInstantiator::VisitSwitchCase(SwitchCase *S) {
|
|
||||||
assert(false && "SwitchCase statements are never directly instantiated");
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult TemplateStmtInstantiator::VisitCaseStmt(CaseStmt *S) {
|
|
||||||
// The case value expressions are not potentially evaluated.
|
|
||||||
EnterExpressionEvaluationContext Unevaluated(SemaRef, Action::Unevaluated);
|
|
||||||
|
|
||||||
// Instantiate left-hand case value.
|
|
||||||
OwningExprResult LHS = SemaRef.InstantiateExpr(S->getLHS(), TemplateArgs);
|
|
||||||
if (LHS.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
// Instantiate right-hand case value (for the GNU case-range extension).
|
|
||||||
OwningExprResult RHS = SemaRef.InstantiateExpr(S->getRHS(), TemplateArgs);
|
|
||||||
if (RHS.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
// Build the case statement.
|
|
||||||
OwningStmtResult Case = SemaRef.ActOnCaseStmt(S->getCaseLoc(),
|
|
||||||
move(LHS),
|
|
||||||
S->getEllipsisLoc(),
|
|
||||||
move(RHS),
|
|
||||||
S->getColonLoc());
|
|
||||||
if (Case.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
// Instantiate the statement following the case
|
|
||||||
OwningStmtResult SubStmt = SemaRef.InstantiateStmt(S->getSubStmt(),
|
|
||||||
TemplateArgs);
|
|
||||||
if (SubStmt.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
SemaRef.ActOnCaseStmtBody(Case.get(), move(SubStmt));
|
|
||||||
return move(Case);
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult
|
|
||||||
TemplateStmtInstantiator::VisitDefaultStmt(DefaultStmt *S) {
|
|
||||||
// Instantiate the statement following the default case
|
|
||||||
OwningStmtResult SubStmt = SemaRef.InstantiateStmt(S->getSubStmt(),
|
|
||||||
TemplateArgs);
|
|
||||||
if (SubStmt.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
return SemaRef.ActOnDefaultStmt(S->getDefaultLoc(),
|
|
||||||
S->getColonLoc(),
|
|
||||||
move(SubStmt),
|
|
||||||
/*CurScope=*/0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult TemplateStmtInstantiator::VisitIfStmt(IfStmt *S) {
|
|
||||||
// Instantiate the condition
|
|
||||||
OwningExprResult Cond = SemaRef.InstantiateExpr(S->getCond(), TemplateArgs);
|
|
||||||
if (Cond.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
Sema::FullExprArg FullCond(FullExpr(Cond));
|
|
||||||
|
|
||||||
// Instantiate the "then" branch.
|
|
||||||
OwningStmtResult Then = SemaRef.InstantiateStmt(S->getThen(), TemplateArgs);
|
|
||||||
if (Then.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
// Instantiate the "else" branch.
|
|
||||||
OwningStmtResult Else = SemaRef.InstantiateStmt(S->getElse(), TemplateArgs);
|
|
||||||
if (Else.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
return SemaRef.ActOnIfStmt(S->getIfLoc(), FullCond, move(Then),
|
|
||||||
S->getElseLoc(), move(Else));
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult
|
|
||||||
TemplateStmtInstantiator::VisitSwitchStmt(SwitchStmt *S) {
|
|
||||||
// Instantiate the condition.
|
|
||||||
OwningExprResult Cond = SemaRef.InstantiateExpr(S->getCond(), TemplateArgs);
|
|
||||||
if (Cond.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
// Start the switch statement itself.
|
|
||||||
OwningStmtResult Switch = SemaRef.ActOnStartOfSwitchStmt(move(Cond));
|
|
||||||
if (Switch.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
// Instantiate the body of the switch statement.
|
|
||||||
OwningStmtResult Body = SemaRef.InstantiateStmt(S->getBody(), TemplateArgs);
|
|
||||||
if (Body.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
// Complete the switch statement.
|
|
||||||
return SemaRef.ActOnFinishSwitchStmt(S->getSwitchLoc(), move(Switch),
|
|
||||||
move(Body));
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult TemplateStmtInstantiator::VisitWhileStmt(WhileStmt *S) {
|
|
||||||
// Instantiate the condition
|
|
||||||
OwningExprResult Cond = SemaRef.InstantiateExpr(S->getCond(), TemplateArgs);
|
|
||||||
if (Cond.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
Sema::FullExprArg FullCond(FullExpr(Cond));
|
|
||||||
|
|
||||||
// Instantiate the body
|
|
||||||
OwningStmtResult Body = SemaRef.InstantiateStmt(S->getBody(), TemplateArgs);
|
|
||||||
if (Body.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
return SemaRef.ActOnWhileStmt(S->getWhileLoc(), FullCond, move(Body));
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult TemplateStmtInstantiator::VisitDoStmt(DoStmt *S) {
|
|
||||||
// Instantiate the condition
|
|
||||||
OwningExprResult Cond = SemaRef.InstantiateExpr(S->getCond(), TemplateArgs);
|
|
||||||
if (Cond.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
// Instantiate the body
|
|
||||||
OwningStmtResult Body = SemaRef.InstantiateStmt(S->getBody(), TemplateArgs);
|
|
||||||
if (Body.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
return SemaRef.ActOnDoStmt(S->getDoLoc(), move(Body), S->getWhileLoc(),
|
|
||||||
SourceLocation(), move(Cond), S->getRParenLoc());
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult TemplateStmtInstantiator::VisitForStmt(ForStmt *S) {
|
|
||||||
// Instantiate the initialization statement
|
|
||||||
OwningStmtResult Init = SemaRef.InstantiateStmt(S->getInit(), TemplateArgs);
|
|
||||||
if (Init.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
// Instantiate the condition
|
|
||||||
OwningExprResult Cond = SemaRef.InstantiateExpr(S->getCond(), TemplateArgs);
|
|
||||||
if (Cond.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
// Instantiate the increment
|
|
||||||
OwningExprResult Inc = SemaRef.InstantiateExpr(S->getInc(), TemplateArgs);
|
|
||||||
if (Inc.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
// Instantiate the body
|
|
||||||
OwningStmtResult Body = SemaRef.InstantiateStmt(S->getBody(), TemplateArgs);
|
|
||||||
if (Body.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
return SemaRef.ActOnForStmt(S->getForLoc(), S->getLParenLoc(),
|
|
||||||
move(Init), move(Cond), move(Inc),
|
|
||||||
S->getRParenLoc(), move(Body));
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult
|
|
||||||
TemplateStmtInstantiator::VisitAsmStmt(AsmStmt *S) {
|
|
||||||
// FIXME: Implement this
|
|
||||||
assert(false && "Cannot instantiate an 'asm' statement");
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
}
|
|
||||||
|
|
||||||
//===----------------------------------------------------------------------===/
|
|
||||||
// C++ statements
|
|
||||||
//===----------------------------------------------------------------------===/
|
|
||||||
Sema::OwningStmtResult
|
|
||||||
TemplateStmtInstantiator::VisitCXXTryStmt(CXXTryStmt *S) {
|
|
||||||
// Instantiate the try block itself.
|
|
||||||
OwningStmtResult TryBlock = VisitCompoundStmt(S->getTryBlock());
|
|
||||||
if (TryBlock.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
// Instantiate the handlers.
|
|
||||||
ASTOwningVector<&ActionBase::DeleteStmt> Handlers(SemaRef);
|
|
||||||
for (unsigned I = 0, N = S->getNumHandlers(); I != N; ++I) {
|
|
||||||
OwningStmtResult Handler = VisitCXXCatchStmt(S->getHandler(I));
|
|
||||||
if (Handler.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
Handlers.push_back(Handler.takeAs<Stmt>());
|
|
||||||
}
|
|
||||||
|
|
||||||
return SemaRef.ActOnCXXTryBlock(S->getTryLoc(), move(TryBlock),
|
|
||||||
move_arg(Handlers));
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult
|
|
||||||
TemplateStmtInstantiator::VisitCXXCatchStmt(CXXCatchStmt *S) {
|
|
||||||
// Instantiate the exception declaration, if any.
|
|
||||||
VarDecl *Var = 0;
|
|
||||||
if (S->getExceptionDecl()) {
|
|
||||||
VarDecl *ExceptionDecl = S->getExceptionDecl();
|
|
||||||
QualType T = SemaRef.InstantiateType(ExceptionDecl->getType(),
|
|
||||||
TemplateArgs,
|
|
||||||
ExceptionDecl->getLocation(),
|
|
||||||
ExceptionDecl->getDeclName());
|
|
||||||
if (T.isNull())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
Var = SemaRef.BuildExceptionDeclaration(0, T,
|
|
||||||
ExceptionDecl->getIdentifier(),
|
|
||||||
ExceptionDecl->getLocation(),
|
|
||||||
/*FIXME: Inaccurate*/
|
|
||||||
SourceRange(ExceptionDecl->getLocation()));
|
|
||||||
if (Var->isInvalidDecl()) {
|
|
||||||
Var->Destroy(SemaRef.Context);
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Introduce the exception declaration into scope.
|
|
||||||
SemaRef.CurrentInstantiationScope->InstantiatedLocal(ExceptionDecl, Var);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instantiate the actual exception handler.
|
|
||||||
OwningStmtResult Handler = Visit(S->getHandlerBlock());
|
|
||||||
if (Handler.isInvalid()) {
|
|
||||||
if (Var)
|
|
||||||
Var->Destroy(SemaRef.Context);
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
}
|
|
||||||
|
|
||||||
return SemaRef.Owned(new (SemaRef.Context) CXXCatchStmt(S->getCatchLoc(),
|
|
||||||
Var,
|
|
||||||
Handler.takeAs<Stmt>()));
|
|
||||||
}
|
|
||||||
|
|
||||||
//===----------------------------------------------------------------------===/
|
|
||||||
// Objective-C statements
|
|
||||||
//===----------------------------------------------------------------------===/
|
|
||||||
Sema::OwningStmtResult
|
|
||||||
TemplateStmtInstantiator::VisitObjCAtFinallyStmt(ObjCAtFinallyStmt *S) {
|
|
||||||
// FIXME: Implement this
|
|
||||||
assert(false && "Cannot instantiate an Objective-C @finally statement");
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult
|
|
||||||
TemplateStmtInstantiator::VisitObjCAtSynchronizedStmt(
|
|
||||||
ObjCAtSynchronizedStmt *S) {
|
|
||||||
// FIXME: Implement this
|
|
||||||
assert(false && "Cannot instantiate an Objective-C @synchronized statement");
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult
|
|
||||||
TemplateStmtInstantiator::VisitObjCAtTryStmt(ObjCAtTryStmt *S) {
|
|
||||||
// FIXME: Implement this
|
|
||||||
assert(false && "Cannot instantiate an Objective-C @try statement");
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult
|
|
||||||
TemplateStmtInstantiator::VisitObjCForCollectionStmt(
|
|
||||||
ObjCForCollectionStmt *S) {
|
|
||||||
// FIXME: Implement this
|
|
||||||
assert(false && "Cannot instantiate an Objective-C \"for\" statement");
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult
|
|
||||||
TemplateStmtInstantiator::VisitObjCAtThrowStmt(ObjCAtThrowStmt *S) {
|
|
||||||
// FIXME: Implement this
|
|
||||||
assert(false && "Cannot instantiate an Objective-C @throw statement");
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult
|
|
||||||
TemplateStmtInstantiator::VisitObjCAtCatchStmt(ObjCAtCatchStmt *S) {
|
|
||||||
// FIXME: Implement this
|
|
||||||
assert(false && "Cannot instantiate an Objective-C @catch statement");
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult TemplateStmtInstantiator::VisitExpr(Expr *E) {
|
|
||||||
Sema::OwningExprResult Result = SemaRef.InstantiateExpr(E, TemplateArgs);
|
|
||||||
if (Result.isInvalid())
|
|
||||||
return SemaRef.StmtError();
|
|
||||||
|
|
||||||
return SemaRef.Owned(Result.takeAs<Stmt>());
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult
|
|
||||||
Sema::InstantiateStmt(Stmt *S, const TemplateArgumentList &TemplateArgs) {
|
|
||||||
if (!S)
|
|
||||||
return Owned((Stmt *)0);
|
|
||||||
|
|
||||||
TemplateStmtInstantiator Instantiator(*this, TemplateArgs);
|
|
||||||
return Instantiator.Visit(S);
|
|
||||||
}
|
|
||||||
|
|
||||||
Sema::OwningStmtResult
|
|
||||||
Sema::InstantiateCompoundStmt(CompoundStmt *S,
|
|
||||||
const TemplateArgumentList &TemplateArgs,
|
|
||||||
bool isStmtExpr) {
|
|
||||||
if (!S)
|
|
||||||
return Owned((Stmt *)0);
|
|
||||||
|
|
||||||
TemplateStmtInstantiator Instantiator(*this, TemplateArgs);
|
|
||||||
ASTOwningVector<&ActionBase::DeleteStmt> Statements(*this);
|
|
||||||
for (CompoundStmt::body_iterator B = S->body_begin(), BEnd = S->body_end();
|
|
||||||
B != BEnd; ++B) {
|
|
||||||
OwningStmtResult Result = Instantiator.Visit(*B);
|
|
||||||
if (Result.isInvalid())
|
|
||||||
return StmtError();
|
|
||||||
|
|
||||||
Statements.push_back(Result.takeAs<Stmt>());
|
|
||||||
}
|
|
||||||
|
|
||||||
return ActOnCompoundStmt(S->getLBracLoc(), S->getRBracLoc(),
|
|
||||||
move_arg(Statements), isStmtExpr);
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
LEVEL = ../../..
|
|
||||||
include $(LEVEL)/Makefile.common
|
|
||||||
|
|
||||||
# Test in all immediate subdirectories if unset.
|
|
||||||
TESTDIRS ?= $(shell echo $(PROJ_SRC_DIR)/*/)
|
|
||||||
|
|
||||||
ifndef TESTARGS
|
|
||||||
ifdef VERBOSE
|
|
||||||
TESTARGS = -v
|
|
||||||
else
|
|
||||||
TESTARGS = -s
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
all::
|
|
||||||
@ PATH=$(ToolDir):$(LLVM_SRC_ROOT)/test/Scripts:$$PATH VG=$(VG) ../utils/test/MultiTestRunner.py $(TESTARGS) $(TESTDIRS)
|
|
||||||
|
|
||||||
clean::
|
|
||||||
@ rm -rf Output/
|
|
||||||
|
|
||||||
.PHONY: all report clean
|
|
@ -1,347 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
"""
|
|
||||||
MultiTestRunner - Harness for running multiple tests in the simple clang style.
|
|
||||||
|
|
||||||
TODO
|
|
||||||
--
|
|
||||||
- Fix Ctrl-c issues
|
|
||||||
- Use a timeout
|
|
||||||
- Detect signalled failures (abort)
|
|
||||||
- Better support for finding tests
|
|
||||||
"""
|
|
||||||
|
|
||||||
# TOD
|
|
||||||
import os, sys, re, random, time
|
|
||||||
import threading
|
|
||||||
import ProgressBar
|
|
||||||
import TestRunner
|
|
||||||
from TestRunner import TestStatus
|
|
||||||
from Queue import Queue
|
|
||||||
|
|
||||||
kTestFileExtensions = set(['.mi','.i','.c','.cpp','.m','.mm','.ll'])
|
|
||||||
|
|
||||||
def getTests(inputs):
|
|
||||||
for path in inputs:
|
|
||||||
if not os.path.exists(path):
|
|
||||||
print >>sys.stderr,"WARNING: Invalid test \"%s\""%(path,)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if os.path.isdir(path):
|
|
||||||
for dirpath,dirnames,filenames in os.walk(path):
|
|
||||||
dotTests = os.path.join(dirpath,'.tests')
|
|
||||||
if os.path.exists(dotTests):
|
|
||||||
for ln in open(dotTests):
|
|
||||||
if ln.strip():
|
|
||||||
yield os.path.join(dirpath,ln.strip())
|
|
||||||
else:
|
|
||||||
# FIXME: This doesn't belong here
|
|
||||||
if 'Output' in dirnames:
|
|
||||||
dirnames.remove('Output')
|
|
||||||
for f in filenames:
|
|
||||||
base,ext = os.path.splitext(f)
|
|
||||||
if ext in kTestFileExtensions:
|
|
||||||
yield os.path.join(dirpath,f)
|
|
||||||
else:
|
|
||||||
yield path
|
|
||||||
|
|
||||||
class TestingProgressDisplay:
|
|
||||||
def __init__(self, opts, numTests, progressBar=None):
|
|
||||||
self.opts = opts
|
|
||||||
self.numTests = numTests
|
|
||||||
self.digits = len(str(self.numTests))
|
|
||||||
self.current = None
|
|
||||||
self.lock = threading.Lock()
|
|
||||||
self.progressBar = progressBar
|
|
||||||
self.progress = 0.
|
|
||||||
|
|
||||||
def update(self, index, tr):
|
|
||||||
# Avoid locking overhead in quiet mode
|
|
||||||
if self.opts.quiet and not tr.failed():
|
|
||||||
return
|
|
||||||
|
|
||||||
# Output lock
|
|
||||||
self.lock.acquire()
|
|
||||||
try:
|
|
||||||
self.handleUpdate(index, tr)
|
|
||||||
finally:
|
|
||||||
self.lock.release()
|
|
||||||
|
|
||||||
def finish(self):
|
|
||||||
if self.progressBar:
|
|
||||||
self.progressBar.clear()
|
|
||||||
elif self.opts.succinct:
|
|
||||||
sys.stdout.write('\n')
|
|
||||||
|
|
||||||
def handleUpdate(self, index, tr):
|
|
||||||
if self.progressBar:
|
|
||||||
if tr.failed():
|
|
||||||
self.progressBar.clear()
|
|
||||||
else:
|
|
||||||
# Force monotonicity
|
|
||||||
self.progress = max(self.progress, float(index)/self.numTests)
|
|
||||||
self.progressBar.update(self.progress, tr.path)
|
|
||||||
return
|
|
||||||
elif self.opts.succinct:
|
|
||||||
if not tr.failed():
|
|
||||||
sys.stdout.write('.')
|
|
||||||
sys.stdout.flush()
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
sys.stdout.write('\n')
|
|
||||||
|
|
||||||
extra = ''
|
|
||||||
if tr.code==TestStatus.Invalid:
|
|
||||||
extra = ' - (Invalid test)'
|
|
||||||
elif tr.code==TestStatus.NoRunLine:
|
|
||||||
extra = ' - (No RUN line)'
|
|
||||||
elif tr.failed():
|
|
||||||
extra = ' - %s'%(TestStatus.getName(tr.code).upper(),)
|
|
||||||
print '%*d/%*d - %s%s'%(self.digits, index+1, self.digits,
|
|
||||||
self.numTests, tr.path, extra)
|
|
||||||
|
|
||||||
if tr.failed() and self.opts.showOutput:
|
|
||||||
TestRunner.cat(tr.testResults, sys.stdout)
|
|
||||||
|
|
||||||
class TestResult:
|
|
||||||
def __init__(self, path, code, testResults):
|
|
||||||
self.path = path
|
|
||||||
self.code = code
|
|
||||||
self.testResults = testResults
|
|
||||||
|
|
||||||
def failed(self):
|
|
||||||
return self.code in (TestStatus.Fail,TestStatus.XPass)
|
|
||||||
|
|
||||||
class TestProvider:
|
|
||||||
def __init__(self, opts, tests, display):
|
|
||||||
self.opts = opts
|
|
||||||
self.tests = tests
|
|
||||||
self.index = 0
|
|
||||||
self.lock = threading.Lock()
|
|
||||||
self.results = [None]*len(self.tests)
|
|
||||||
self.startTime = time.time()
|
|
||||||
self.progress = display
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
self.lock.acquire()
|
|
||||||
try:
|
|
||||||
if self.opts.maxTime is not None:
|
|
||||||
if time.time() - self.startTime > self.opts.maxTime:
|
|
||||||
return None
|
|
||||||
if self.index >= len(self.tests):
|
|
||||||
return None
|
|
||||||
item = self.tests[self.index],self.index
|
|
||||||
self.index += 1
|
|
||||||
return item
|
|
||||||
finally:
|
|
||||||
self.lock.release()
|
|
||||||
|
|
||||||
def setResult(self, index, result):
|
|
||||||
self.results[index] = result
|
|
||||||
self.progress.update(index, result)
|
|
||||||
|
|
||||||
class Tester(threading.Thread):
|
|
||||||
def __init__(self, provider):
|
|
||||||
threading.Thread.__init__(self)
|
|
||||||
self.provider = provider
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
while 1:
|
|
||||||
item = self.provider.get()
|
|
||||||
if item is None:
|
|
||||||
break
|
|
||||||
self.runTest(item)
|
|
||||||
|
|
||||||
def runTest(self, (path,index)):
|
|
||||||
command = path
|
|
||||||
# Use hand concatentation here because we want to override
|
|
||||||
# absolute paths.
|
|
||||||
output = 'Output/' + path + '.out'
|
|
||||||
testname = path
|
|
||||||
testresults = 'Output/' + path + '.testresults'
|
|
||||||
TestRunner.mkdir_p(os.path.dirname(testresults))
|
|
||||||
numTests = len(self.provider.tests)
|
|
||||||
digits = len(str(numTests))
|
|
||||||
code = None
|
|
||||||
try:
|
|
||||||
opts = self.provider.opts
|
|
||||||
if opts.debugDoNotTest:
|
|
||||||
code = None
|
|
||||||
else:
|
|
||||||
code = TestRunner.runOneTest(path, command, output, testname,
|
|
||||||
opts.clang, opts.clangcc,
|
|
||||||
useValgrind=opts.useValgrind,
|
|
||||||
useDGCompat=opts.useDGCompat,
|
|
||||||
useScript=opts.testScript,
|
|
||||||
output=open(testresults,'w'))
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
# This is a sad hack. Unfortunately subprocess goes
|
|
||||||
# bonkers with ctrl-c and we start forking merrily.
|
|
||||||
print 'Ctrl-C detected, goodbye.'
|
|
||||||
os.kill(0,9)
|
|
||||||
|
|
||||||
self.provider.setResult(index, TestResult(path, code, testresults))
|
|
||||||
|
|
||||||
def detectCPUs():
|
|
||||||
"""
|
|
||||||
Detects the number of CPUs on a system. Cribbed from pp.
|
|
||||||
"""
|
|
||||||
# Linux, Unix and MacOS:
|
|
||||||
if hasattr(os, "sysconf"):
|
|
||||||
if os.sysconf_names.has_key("SC_NPROCESSORS_ONLN"):
|
|
||||||
# Linux & Unix:
|
|
||||||
ncpus = os.sysconf("SC_NPROCESSORS_ONLN")
|
|
||||||
if isinstance(ncpus, int) and ncpus > 0:
|
|
||||||
return ncpus
|
|
||||||
else: # OSX:
|
|
||||||
return int(os.popen2("sysctl -n hw.ncpu")[1].read())
|
|
||||||
# Windows:
|
|
||||||
if os.environ.has_key("NUMBER_OF_PROCESSORS"):
|
|
||||||
ncpus = int(os.environ["NUMBER_OF_PROCESSORS"]);
|
|
||||||
if ncpus > 0:
|
|
||||||
return ncpus
|
|
||||||
return 1 # Default
|
|
||||||
|
|
||||||
def main():
|
|
||||||
global options
|
|
||||||
from optparse import OptionParser
|
|
||||||
parser = OptionParser("usage: %prog [options] {inputs}")
|
|
||||||
parser.add_option("-j", "--threads", dest="numThreads",
|
|
||||||
help="Number of testing threads",
|
|
||||||
type=int, action="store",
|
|
||||||
default=detectCPUs())
|
|
||||||
parser.add_option("", "--clang", dest="clang",
|
|
||||||
help="Program to use as \"clang\"",
|
|
||||||
action="store", default=None)
|
|
||||||
parser.add_option("", "--clang-cc", dest="clangcc",
|
|
||||||
help="Program to use as \"clang-cc\"",
|
|
||||||
action="store", default=None)
|
|
||||||
parser.add_option("", "--vg", dest="useValgrind",
|
|
||||||
help="Run tests under valgrind",
|
|
||||||
action="store_true", default=False)
|
|
||||||
parser.add_option("", "--dg", dest="useDGCompat",
|
|
||||||
help="Use llvm dejagnu compatibility mode",
|
|
||||||
action="store_true", default=False)
|
|
||||||
parser.add_option("", "--script", dest="testScript",
|
|
||||||
help="Default script to use",
|
|
||||||
action="store", default=None)
|
|
||||||
parser.add_option("-v", "--verbose", dest="showOutput",
|
|
||||||
help="Show all test output",
|
|
||||||
action="store_true", default=False)
|
|
||||||
parser.add_option("-q", "--quiet", dest="quiet",
|
|
||||||
help="Suppress no error output",
|
|
||||||
action="store_true", default=False)
|
|
||||||
parser.add_option("-s", "--succinct", dest="succinct",
|
|
||||||
help="Reduce amount of output",
|
|
||||||
action="store_true", default=False)
|
|
||||||
parser.add_option("", "--max-tests", dest="maxTests",
|
|
||||||
help="Maximum number of tests to run",
|
|
||||||
action="store", type=int, default=None)
|
|
||||||
parser.add_option("", "--max-time", dest="maxTime",
|
|
||||||
help="Maximum time to spend testing (in seconds)",
|
|
||||||
action="store", type=float, default=None)
|
|
||||||
parser.add_option("", "--shuffle", dest="shuffle",
|
|
||||||
help="Run tests in random order",
|
|
||||||
action="store_true", default=False)
|
|
||||||
parser.add_option("", "--seed", dest="seed",
|
|
||||||
help="Seed for random number generator (default: random)",
|
|
||||||
action="store", default=None)
|
|
||||||
parser.add_option("", "--no-progress-bar", dest="useProgressBar",
|
|
||||||
help="Do not use curses based progress bar",
|
|
||||||
action="store_false", default=True)
|
|
||||||
parser.add_option("", "--debug-do-not-test", dest="debugDoNotTest",
|
|
||||||
help="DEBUG: Skip running actual test script",
|
|
||||||
action="store_true", default=False)
|
|
||||||
parser.add_option("", "--path", dest="path",
|
|
||||||
help="Additional paths to add to testing environment",
|
|
||||||
action="store", type=str, default=None)
|
|
||||||
|
|
||||||
(opts, args) = parser.parse_args()
|
|
||||||
|
|
||||||
if not args:
|
|
||||||
parser.error('No inputs specified')
|
|
||||||
|
|
||||||
if opts.clang is None:
|
|
||||||
opts.clang = TestRunner.inferClang()
|
|
||||||
if opts.clangcc is None:
|
|
||||||
opts.clangcc = TestRunner.inferClangCC(opts.clang)
|
|
||||||
|
|
||||||
# FIXME: It could be worth loading these in parallel with testing.
|
|
||||||
allTests = list(getTests(args))
|
|
||||||
allTests.sort()
|
|
||||||
|
|
||||||
tests = allTests
|
|
||||||
if opts.seed is not None:
|
|
||||||
try:
|
|
||||||
seed = int(opts.seed)
|
|
||||||
except:
|
|
||||||
parser.error('--seed argument should be an integer')
|
|
||||||
random.seed(seed)
|
|
||||||
if opts.shuffle:
|
|
||||||
random.shuffle(tests)
|
|
||||||
if opts.maxTests is not None:
|
|
||||||
tests = tests[:opts.maxTests]
|
|
||||||
if opts.path is not None:
|
|
||||||
os.environ["PATH"] = opts.path + ":" + os.environ["PATH"];
|
|
||||||
|
|
||||||
extra = ''
|
|
||||||
if len(tests) != len(allTests):
|
|
||||||
extra = ' of %d'%(len(allTests),)
|
|
||||||
header = '-- Testing: %d%s tests, %d threads --'%(len(tests),extra,
|
|
||||||
opts.numThreads)
|
|
||||||
|
|
||||||
progressBar = None
|
|
||||||
if not opts.quiet:
|
|
||||||
if opts.useProgressBar:
|
|
||||||
try:
|
|
||||||
tc = ProgressBar.TerminalController()
|
|
||||||
progressBar = ProgressBar.ProgressBar(tc, header)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not progressBar:
|
|
||||||
print header
|
|
||||||
|
|
||||||
display = TestingProgressDisplay(opts, len(tests), progressBar)
|
|
||||||
provider = TestProvider(opts, tests, display)
|
|
||||||
|
|
||||||
testers = [Tester(provider) for i in range(opts.numThreads)]
|
|
||||||
startTime = time.time()
|
|
||||||
for t in testers:
|
|
||||||
t.start()
|
|
||||||
try:
|
|
||||||
for t in testers:
|
|
||||||
t.join()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
display.finish()
|
|
||||||
|
|
||||||
if not opts.quiet:
|
|
||||||
print 'Testing Time: %.2fs'%(time.time() - startTime)
|
|
||||||
|
|
||||||
# List test results organized organized by kind.
|
|
||||||
byCode = {}
|
|
||||||
for t in provider.results:
|
|
||||||
if t:
|
|
||||||
if t.code not in byCode:
|
|
||||||
byCode[t.code] = []
|
|
||||||
byCode[t.code].append(t)
|
|
||||||
for title,code in (('Expected Failures', TestStatus.XFail),
|
|
||||||
('Unexpected Passing Tests', TestStatus.XPass),
|
|
||||||
('Failing Tests', TestStatus.Fail)):
|
|
||||||
elts = byCode.get(code)
|
|
||||||
if not elts:
|
|
||||||
continue
|
|
||||||
print '*'*20
|
|
||||||
print '%s (%d):' % (title, len(elts))
|
|
||||||
for tr in elts:
|
|
||||||
print '\t%s'%(tr.path,)
|
|
||||||
|
|
||||||
numFailures = len(byCode.get(TestStatus.Fail,[]))
|
|
||||||
if numFailures:
|
|
||||||
print '\nFailures: %d' % (numFailures,)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
main()
|
|
@ -1,227 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
# Source: http://code.activestate.com/recipes/475116/, with
|
|
||||||
# modifications by Daniel Dunbar.
|
|
||||||
|
|
||||||
import sys, re, time
|
|
||||||
|
|
||||||
class TerminalController:
|
|
||||||
"""
|
|
||||||
A class that can be used to portably generate formatted output to
|
|
||||||
a terminal.
|
|
||||||
|
|
||||||
`TerminalController` defines a set of instance variables whose
|
|
||||||
values are initialized to the control sequence necessary to
|
|
||||||
perform a given action. These can be simply included in normal
|
|
||||||
output to the terminal:
|
|
||||||
|
|
||||||
>>> term = TerminalController()
|
|
||||||
>>> print 'This is '+term.GREEN+'green'+term.NORMAL
|
|
||||||
|
|
||||||
Alternatively, the `render()` method can used, which replaces
|
|
||||||
'${action}' with the string required to perform 'action':
|
|
||||||
|
|
||||||
>>> term = TerminalController()
|
|
||||||
>>> print term.render('This is ${GREEN}green${NORMAL}')
|
|
||||||
|
|
||||||
If the terminal doesn't support a given action, then the value of
|
|
||||||
the corresponding instance variable will be set to ''. As a
|
|
||||||
result, the above code will still work on terminals that do not
|
|
||||||
support color, except that their output will not be colored.
|
|
||||||
Also, this means that you can test whether the terminal supports a
|
|
||||||
given action by simply testing the truth value of the
|
|
||||||
corresponding instance variable:
|
|
||||||
|
|
||||||
>>> term = TerminalController()
|
|
||||||
>>> if term.CLEAR_SCREEN:
|
|
||||||
... print 'This terminal supports clearning the screen.'
|
|
||||||
|
|
||||||
Finally, if the width and height of the terminal are known, then
|
|
||||||
they will be stored in the `COLS` and `LINES` attributes.
|
|
||||||
"""
|
|
||||||
# Cursor movement:
|
|
||||||
BOL = '' #: Move the cursor to the beginning of the line
|
|
||||||
UP = '' #: Move the cursor up one line
|
|
||||||
DOWN = '' #: Move the cursor down one line
|
|
||||||
LEFT = '' #: Move the cursor left one char
|
|
||||||
RIGHT = '' #: Move the cursor right one char
|
|
||||||
|
|
||||||
# Deletion:
|
|
||||||
CLEAR_SCREEN = '' #: Clear the screen and move to home position
|
|
||||||
CLEAR_EOL = '' #: Clear to the end of the line.
|
|
||||||
CLEAR_BOL = '' #: Clear to the beginning of the line.
|
|
||||||
CLEAR_EOS = '' #: Clear to the end of the screen
|
|
||||||
|
|
||||||
# Output modes:
|
|
||||||
BOLD = '' #: Turn on bold mode
|
|
||||||
BLINK = '' #: Turn on blink mode
|
|
||||||
DIM = '' #: Turn on half-bright mode
|
|
||||||
REVERSE = '' #: Turn on reverse-video mode
|
|
||||||
NORMAL = '' #: Turn off all modes
|
|
||||||
|
|
||||||
# Cursor display:
|
|
||||||
HIDE_CURSOR = '' #: Make the cursor invisible
|
|
||||||
SHOW_CURSOR = '' #: Make the cursor visible
|
|
||||||
|
|
||||||
# Terminal size:
|
|
||||||
COLS = None #: Width of the terminal (None for unknown)
|
|
||||||
LINES = None #: Height of the terminal (None for unknown)
|
|
||||||
|
|
||||||
# Foreground colors:
|
|
||||||
BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = ''
|
|
||||||
|
|
||||||
# Background colors:
|
|
||||||
BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = ''
|
|
||||||
BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = ''
|
|
||||||
|
|
||||||
_STRING_CAPABILITIES = """
|
|
||||||
BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1
|
|
||||||
CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold
|
|
||||||
BLINK=blink DIM=dim REVERSE=rev UNDERLINE=smul NORMAL=sgr0
|
|
||||||
HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split()
|
|
||||||
_COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split()
|
|
||||||
_ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split()
|
|
||||||
|
|
||||||
def __init__(self, term_stream=sys.stdout):
|
|
||||||
"""
|
|
||||||
Create a `TerminalController` and initialize its attributes
|
|
||||||
with appropriate values for the current terminal.
|
|
||||||
`term_stream` is the stream that will be used for terminal
|
|
||||||
output; if this stream is not a tty, then the terminal is
|
|
||||||
assumed to be a dumb terminal (i.e., have no capabilities).
|
|
||||||
"""
|
|
||||||
# Curses isn't available on all platforms
|
|
||||||
try: import curses
|
|
||||||
except: return
|
|
||||||
|
|
||||||
# If the stream isn't a tty, then assume it has no capabilities.
|
|
||||||
if not term_stream.isatty(): return
|
|
||||||
|
|
||||||
# Check the terminal type. If we fail, then assume that the
|
|
||||||
# terminal has no capabilities.
|
|
||||||
try: curses.setupterm()
|
|
||||||
except: return
|
|
||||||
|
|
||||||
# Look up numeric capabilities.
|
|
||||||
self.COLS = curses.tigetnum('cols')
|
|
||||||
self.LINES = curses.tigetnum('lines')
|
|
||||||
|
|
||||||
# Look up string capabilities.
|
|
||||||
for capability in self._STRING_CAPABILITIES:
|
|
||||||
(attrib, cap_name) = capability.split('=')
|
|
||||||
setattr(self, attrib, self._tigetstr(cap_name) or '')
|
|
||||||
|
|
||||||
# Colors
|
|
||||||
set_fg = self._tigetstr('setf')
|
|
||||||
if set_fg:
|
|
||||||
for i,color in zip(range(len(self._COLORS)), self._COLORS):
|
|
||||||
setattr(self, color, curses.tparm(set_fg, i) or '')
|
|
||||||
set_fg_ansi = self._tigetstr('setaf')
|
|
||||||
if set_fg_ansi:
|
|
||||||
for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
|
|
||||||
setattr(self, color, curses.tparm(set_fg_ansi, i) or '')
|
|
||||||
set_bg = self._tigetstr('setb')
|
|
||||||
if set_bg:
|
|
||||||
for i,color in zip(range(len(self._COLORS)), self._COLORS):
|
|
||||||
setattr(self, 'BG_'+color, curses.tparm(set_bg, i) or '')
|
|
||||||
set_bg_ansi = self._tigetstr('setab')
|
|
||||||
if set_bg_ansi:
|
|
||||||
for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
|
|
||||||
setattr(self, 'BG_'+color, curses.tparm(set_bg_ansi, i) or '')
|
|
||||||
|
|
||||||
def _tigetstr(self, cap_name):
|
|
||||||
# String capabilities can include "delays" of the form "$<2>".
|
|
||||||
# For any modern terminal, we should be able to just ignore
|
|
||||||
# these, so strip them out.
|
|
||||||
import curses
|
|
||||||
cap = curses.tigetstr(cap_name) or ''
|
|
||||||
return re.sub(r'\$<\d+>[/*]?', '', cap)
|
|
||||||
|
|
||||||
def render(self, template):
|
|
||||||
"""
|
|
||||||
Replace each $-substitutions in the given template string with
|
|
||||||
the corresponding terminal control string (if it's defined) or
|
|
||||||
'' (if it's not).
|
|
||||||
"""
|
|
||||||
return re.sub(r'\$\$|\${\w+}', self._render_sub, template)
|
|
||||||
|
|
||||||
def _render_sub(self, match):
|
|
||||||
s = match.group()
|
|
||||||
if s == '$$': return s
|
|
||||||
else: return getattr(self, s[2:-1])
|
|
||||||
|
|
||||||
#######################################################################
|
|
||||||
# Example use case: progress bar
|
|
||||||
#######################################################################
|
|
||||||
|
|
||||||
class ProgressBar:
|
|
||||||
"""
|
|
||||||
A 3-line progress bar, which looks like::
|
|
||||||
|
|
||||||
Header
|
|
||||||
20% [===========----------------------------------]
|
|
||||||
progress message
|
|
||||||
|
|
||||||
The progress bar is colored, if the terminal supports color
|
|
||||||
output; and adjusts to the width of the terminal.
|
|
||||||
"""
|
|
||||||
BAR = '%s${GREEN}[${BOLD}%s%s${NORMAL}${GREEN}]${NORMAL}%s\n'
|
|
||||||
HEADER = '${BOLD}${CYAN}%s${NORMAL}\n\n'
|
|
||||||
|
|
||||||
def __init__(self, term, header, useETA=True):
|
|
||||||
self.term = term
|
|
||||||
if not (self.term.CLEAR_EOL and self.term.UP and self.term.BOL):
|
|
||||||
raise ValueError("Terminal isn't capable enough -- you "
|
|
||||||
"should use a simpler progress dispaly.")
|
|
||||||
self.width = self.term.COLS or 75
|
|
||||||
self.bar = term.render(self.BAR)
|
|
||||||
self.header = self.term.render(self.HEADER % header.center(self.width))
|
|
||||||
self.cleared = 1 #: true if we haven't drawn the bar yet.
|
|
||||||
self.useETA = useETA
|
|
||||||
if self.useETA:
|
|
||||||
self.startTime = time.time()
|
|
||||||
self.update(0, '')
|
|
||||||
|
|
||||||
def update(self, percent, message):
|
|
||||||
if self.cleared:
|
|
||||||
sys.stdout.write(self.header)
|
|
||||||
self.cleared = 0
|
|
||||||
prefix = '%3d%% ' % (percent*100,)
|
|
||||||
suffix = ''
|
|
||||||
if self.useETA:
|
|
||||||
elapsed = time.time() - self.startTime
|
|
||||||
if percent > .0001 and elapsed > 1:
|
|
||||||
total = elapsed / percent
|
|
||||||
eta = int(total - elapsed)
|
|
||||||
h = eta//3600.
|
|
||||||
m = (eta//60) % 60
|
|
||||||
s = eta % 60
|
|
||||||
suffix = ' ETA: %02d:%02d:%02d'%(h,m,s)
|
|
||||||
barWidth = self.width - len(prefix) - len(suffix) - 2
|
|
||||||
n = int(barWidth*percent)
|
|
||||||
if len(message) < self.width:
|
|
||||||
message = message + ' '*(self.width - len(message))
|
|
||||||
else:
|
|
||||||
message = '... ' + message[-(self.width-4):]
|
|
||||||
sys.stdout.write(
|
|
||||||
self.term.BOL + self.term.UP + self.term.CLEAR_EOL +
|
|
||||||
(self.bar % (prefix, '='*n, '-'*(barWidth-n), suffix)) +
|
|
||||||
self.term.CLEAR_EOL + message)
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
if not self.cleared:
|
|
||||||
sys.stdout.write(self.term.BOL + self.term.CLEAR_EOL +
|
|
||||||
self.term.UP + self.term.CLEAR_EOL +
|
|
||||||
self.term.UP + self.term.CLEAR_EOL)
|
|
||||||
self.cleared = 1
|
|
||||||
|
|
||||||
def test():
|
|
||||||
import time
|
|
||||||
tc = TerminalController()
|
|
||||||
p = ProgressBar(tc, 'Tests')
|
|
||||||
for i in range(101):
|
|
||||||
p.update(i/100., str(i))
|
|
||||||
time.sleep(.3)
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
test()
|
|
@ -1,296 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
#
|
|
||||||
# TestRunner.py - This script is used to run arbitrary unit tests. Unit
|
|
||||||
# tests must contain the command used to run them in the input file, starting
|
|
||||||
# immediately after a "RUN:" string.
|
|
||||||
#
|
|
||||||
# This runner recognizes and replaces the following strings in the command:
|
|
||||||
#
|
|
||||||
# %s - Replaced with the input name of the program, or the program to
|
|
||||||
# execute, as appropriate.
|
|
||||||
# %S - Replaced with the directory where the input resides.
|
|
||||||
# %llvmgcc - llvm-gcc command
|
|
||||||
# %llvmgxx - llvm-g++ command
|
|
||||||
# %prcontext - prcontext.tcl script
|
|
||||||
# %t - temporary file name (derived from testcase name)
|
|
||||||
#
|
|
||||||
|
|
||||||
import errno
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import signal
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
|
|
||||||
# Increase determinism for things that use the terminal width.
|
|
||||||
#
|
|
||||||
# FIXME: Find a better place for this hack.
|
|
||||||
os.environ['COLUMNS'] = '0'
|
|
||||||
|
|
||||||
class TestStatus:
|
|
||||||
Pass = 0
|
|
||||||
XFail = 1
|
|
||||||
Fail = 2
|
|
||||||
XPass = 3
|
|
||||||
NoRunLine = 4
|
|
||||||
Invalid = 5
|
|
||||||
|
|
||||||
kNames = ['Pass','XFail','Fail','XPass','NoRunLine','Invalid']
|
|
||||||
@staticmethod
|
|
||||||
def getName(code):
|
|
||||||
return TestStatus.kNames[code]
|
|
||||||
|
|
||||||
def mkdir_p(path):
|
|
||||||
if not path:
|
|
||||||
pass
|
|
||||||
elif os.path.exists(path):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
parent = os.path.dirname(path)
|
|
||||||
if parent != path:
|
|
||||||
mkdir_p(parent)
|
|
||||||
try:
|
|
||||||
os.mkdir(path)
|
|
||||||
except OSError,e:
|
|
||||||
if e.errno != errno.EEXIST:
|
|
||||||
raise
|
|
||||||
|
|
||||||
def remove(path):
|
|
||||||
try:
|
|
||||||
os.remove(path)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def cat(path, output):
|
|
||||||
f = open(path)
|
|
||||||
output.writelines(f)
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
def runOneTest(FILENAME, SUBST, OUTPUT, TESTNAME, CLANG, CLANGCC,
|
|
||||||
useValgrind=False,
|
|
||||||
useDGCompat=False,
|
|
||||||
useScript=None,
|
|
||||||
output=sys.stdout):
|
|
||||||
if useValgrind:
|
|
||||||
VG_OUTPUT = '%s.vg'%(OUTPUT,)
|
|
||||||
if os.path.exists:
|
|
||||||
remove(VG_OUTPUT)
|
|
||||||
CLANG = 'valgrind --leak-check=full --quiet --log-file=%s %s'%(VG_OUTPUT, CLANG)
|
|
||||||
|
|
||||||
# Create the output directory if it does not already exist.
|
|
||||||
mkdir_p(os.path.dirname(OUTPUT))
|
|
||||||
|
|
||||||
# FIXME
|
|
||||||
#ulimit -t 40
|
|
||||||
|
|
||||||
# FIXME: Load script once
|
|
||||||
# FIXME: Support "short" script syntax
|
|
||||||
|
|
||||||
if useScript:
|
|
||||||
scriptFile = useScript
|
|
||||||
else:
|
|
||||||
# See if we have a per-dir test script.
|
|
||||||
dirScriptFile = os.path.join(os.path.dirname(FILENAME), 'test.script')
|
|
||||||
if os.path.exists(dirScriptFile):
|
|
||||||
scriptFile = dirScriptFile
|
|
||||||
else:
|
|
||||||
scriptFile = FILENAME
|
|
||||||
|
|
||||||
# Verify the script contains a run line.
|
|
||||||
for ln in open(scriptFile):
|
|
||||||
if 'RUN:' in ln:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
print >>output, "******************** TEST '%s' HAS NO RUN LINE! ********************"%(TESTNAME,)
|
|
||||||
output.flush()
|
|
||||||
return TestStatus.NoRunLine
|
|
||||||
|
|
||||||
OUTPUT = os.path.abspath(OUTPUT)
|
|
||||||
FILENAME = os.path.abspath(FILENAME)
|
|
||||||
SCRIPT = OUTPUT + '.script'
|
|
||||||
TEMPOUTPUT = OUTPUT + '.tmp'
|
|
||||||
|
|
||||||
substitutions = [('%s',SUBST),
|
|
||||||
('%S',os.path.dirname(SUBST)),
|
|
||||||
('%llvmgcc','llvm-gcc -emit-llvm -w'),
|
|
||||||
('%llvmgxx','llvm-g++ -emit-llvm -w'),
|
|
||||||
('%prcontext','prcontext.tcl'),
|
|
||||||
('%t',TEMPOUTPUT),
|
|
||||||
(' clang ', ' ' + CLANG + ' '),
|
|
||||||
(' clang-cc ', ' ' + CLANGCC + ' ')]
|
|
||||||
scriptLines = []
|
|
||||||
xfailLines = []
|
|
||||||
for ln in open(scriptFile):
|
|
||||||
if 'RUN:' in ln:
|
|
||||||
# Isolate run parameters
|
|
||||||
index = ln.index('RUN:')
|
|
||||||
ln = ln[index+4:]
|
|
||||||
|
|
||||||
# Apply substitutions
|
|
||||||
for a,b in substitutions:
|
|
||||||
ln = ln.replace(a,b)
|
|
||||||
|
|
||||||
if useDGCompat:
|
|
||||||
ln = re.sub(r'\{(.*)\}', r'"\1"', ln)
|
|
||||||
scriptLines.append(ln)
|
|
||||||
elif 'XFAIL' in ln:
|
|
||||||
xfailLines.append(ln)
|
|
||||||
|
|
||||||
if xfailLines:
|
|
||||||
print >>output, "XFAILED '%s':"%(TESTNAME,)
|
|
||||||
output.writelines(xfailLines)
|
|
||||||
|
|
||||||
# Write script file
|
|
||||||
f = open(SCRIPT,'w')
|
|
||||||
f.write(''.join(scriptLines))
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
outputFile = open(OUTPUT,'w')
|
|
||||||
p = None
|
|
||||||
try:
|
|
||||||
p = subprocess.Popen(["/bin/sh",SCRIPT],
|
|
||||||
cwd=os.path.dirname(FILENAME),
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE)
|
|
||||||
out,err = p.communicate()
|
|
||||||
outputFile.write(out)
|
|
||||||
outputFile.write(err)
|
|
||||||
SCRIPT_STATUS = p.wait()
|
|
||||||
|
|
||||||
# Detect Ctrl-C in subprocess.
|
|
||||||
if SCRIPT_STATUS == -signal.SIGINT:
|
|
||||||
raise KeyboardInterrupt
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
raise
|
|
||||||
outputFile.close()
|
|
||||||
|
|
||||||
if xfailLines:
|
|
||||||
SCRIPT_STATUS = not SCRIPT_STATUS
|
|
||||||
|
|
||||||
if useValgrind:
|
|
||||||
VG_STATUS = len(list(open(VG_OUTPUT)))
|
|
||||||
else:
|
|
||||||
VG_STATUS = 0
|
|
||||||
|
|
||||||
if SCRIPT_STATUS or VG_STATUS:
|
|
||||||
print >>output, "******************** TEST '%s' FAILED! ********************"%(TESTNAME,)
|
|
||||||
print >>output, "Command: "
|
|
||||||
output.writelines(scriptLines)
|
|
||||||
if not SCRIPT_STATUS:
|
|
||||||
print >>output, "Output:"
|
|
||||||
else:
|
|
||||||
print >>output, "Incorrect Output:"
|
|
||||||
cat(OUTPUT, output)
|
|
||||||
if VG_STATUS:
|
|
||||||
print >>output, "Valgrind Output:"
|
|
||||||
cat(VG_OUTPUT, output)
|
|
||||||
print >>output, "******************** TEST '%s' FAILED! ********************"%(TESTNAME,)
|
|
||||||
output.flush()
|
|
||||||
if xfailLines:
|
|
||||||
return TestStatus.XPass
|
|
||||||
else:
|
|
||||||
return TestStatus.Fail
|
|
||||||
|
|
||||||
if xfailLines:
|
|
||||||
return TestStatus.XFail
|
|
||||||
else:
|
|
||||||
return TestStatus.Pass
|
|
||||||
|
|
||||||
def capture(args):
|
|
||||||
p = subprocess.Popen(args, stdout=subprocess.PIPE)
|
|
||||||
out,_ = p.communicate()
|
|
||||||
return out
|
|
||||||
|
|
||||||
def which(command):
|
|
||||||
# Would be nice if Python had a lib function for this.
|
|
||||||
res = capture(['which',command])
|
|
||||||
res = res.strip()
|
|
||||||
if res and os.path.exists(res):
|
|
||||||
return res
|
|
||||||
return None
|
|
||||||
|
|
||||||
def inferClang():
|
|
||||||
# Determine which clang to use.
|
|
||||||
clang = os.getenv('CLANG')
|
|
||||||
|
|
||||||
# If the user set clang in the environment, definitely use that and don't
|
|
||||||
# try to validate.
|
|
||||||
if clang:
|
|
||||||
return clang
|
|
||||||
|
|
||||||
# Otherwise look in the path.
|
|
||||||
clang = which('clang')
|
|
||||||
|
|
||||||
if not clang:
|
|
||||||
print >>sys.stderr, "error: couldn't find 'clang' program, try setting CLANG in your environment"
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
return clang
|
|
||||||
|
|
||||||
def inferClangCC(clang):
|
|
||||||
clangcc = os.getenv('CLANGCC')
|
|
||||||
|
|
||||||
# If the user set clang in the environment, definitely use that and don't
|
|
||||||
# try to validate.
|
|
||||||
if clangcc:
|
|
||||||
return clangcc
|
|
||||||
|
|
||||||
# Otherwise try adding -cc since we expect to be looking in a build
|
|
||||||
# directory.
|
|
||||||
clangcc = which(clang + '-cc')
|
|
||||||
if not clangcc:
|
|
||||||
# Otherwise ask clang.
|
|
||||||
res = capture([clang, '-print-prog-name=clang-cc'])
|
|
||||||
res = res.strip()
|
|
||||||
if res and os.path.exists(res):
|
|
||||||
clangcc = res
|
|
||||||
|
|
||||||
if not clangcc:
|
|
||||||
print >>sys.stderr, "error: couldn't find 'clang-cc' program, try setting CLANGCC in your environment"
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
return clangcc
|
|
||||||
|
|
||||||
def main():
|
|
||||||
global options
|
|
||||||
from optparse import OptionParser
|
|
||||||
parser = OptionParser("usage: %prog [options] {tests}")
|
|
||||||
parser.add_option("", "--clang", dest="clang",
|
|
||||||
help="Program to use as \"clang\"",
|
|
||||||
action="store", default=None)
|
|
||||||
parser.add_option("", "--clang-cc", dest="clangcc",
|
|
||||||
help="Program to use as \"clang-cc\"",
|
|
||||||
action="store", default=None)
|
|
||||||
parser.add_option("", "--vg", dest="useValgrind",
|
|
||||||
help="Run tests under valgrind",
|
|
||||||
action="store_true", default=False)
|
|
||||||
parser.add_option("", "--dg", dest="useDGCompat",
|
|
||||||
help="Use llvm dejagnu compatibility mode",
|
|
||||||
action="store_true", default=False)
|
|
||||||
(opts, args) = parser.parse_args()
|
|
||||||
|
|
||||||
if not args:
|
|
||||||
parser.error('No tests specified')
|
|
||||||
|
|
||||||
if opts.clang is None:
|
|
||||||
opts.clang = inferClang()
|
|
||||||
if opts.clangcc is None:
|
|
||||||
opts.clangcc = inferClangCC(opts.clang)
|
|
||||||
|
|
||||||
for path in args:
|
|
||||||
command = path
|
|
||||||
# Use hand concatentation here because we want to override
|
|
||||||
# absolute paths.
|
|
||||||
output = 'Output/' + path + '.out'
|
|
||||||
testname = path
|
|
||||||
|
|
||||||
res = runOneTest(path, command, output, testname,
|
|
||||||
opts.clang, opts.clangcc,
|
|
||||||
useValgrind=opts.useValgrind,
|
|
||||||
useDGCompat=opts.useDGCompat,
|
|
||||||
useScript=os.getenv("TEST_SCRIPT"))
|
|
||||||
|
|
||||||
sys.exit(res == TestStatus.Fail or res == TestStatus.XPass)
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
main()
|
|
Loading…
Reference in New Issue
Block a user