aboutsummaryrefslogtreecommitdiff
path: root/utils
diff options
context:
space:
mode:
authorDimitry Andric <dim@FreeBSD.org>2018-07-28 11:06:01 +0000
committerDimitry Andric <dim@FreeBSD.org>2018-07-28 11:06:01 +0000
commit486754660bb926339aefcf012a3f848592babb8b (patch)
treeecdbc446c9876f4f120f701c243373cd3cb43db3 /utils
parent55e6d896ad333f07bb3b1ba487df214fc268a4ab (diff)
downloadsrc-486754660bb926339aefcf012a3f848592babb8b.tar.gz
src-486754660bb926339aefcf012a3f848592babb8b.zip
Vendor import of clang trunk r338150:vendor/clang/clang-trunk-r338150
Notes
Notes: svn path=/vendor/clang/dist/; revision=336815 svn path=/vendor/clang/clang-trunk-r338150/; revision=336816; tag=vendor/clang/clang-trunk-r338150
Diffstat (limited to 'utils')
-rw-r--r--utils/TableGen/ClangAttrEmitter.cpp297
-rw-r--r--utils/TableGen/ClangCommentHTMLNamedCharacterReferenceEmitter.cpp2
-rw-r--r--utils/TableGen/ClangDiagnosticsEmitter.cpp1083
-rw-r--r--utils/TableGen/ClangOptionDocEmitter.cpp28
-rw-r--r--utils/TableGen/ClangSACheckersEmitter.cpp2
-rw-r--r--utils/TableGen/NeonEmitter.cpp180
-rw-r--r--utils/TableGen/TableGen.cpp5
-rw-r--r--utils/TableGen/TableGenBackends.h1
-rwxr-xr-xutils/analyzer/CmpRuns.py191
-rw-r--r--utils/analyzer/SATestAdd.py4
-rw-r--r--utils/analyzer/SATestBuild.py272
-rwxr-xr-xutils/analyzer/SATestUpdateDiffs.py37
-rw-r--r--utils/analyzer/SATestUtils.py21
-rwxr-xr-xutils/bash-autocomplete.sh46
-rwxr-xr-xutils/check_cfc/check_cfc.py2
-rwxr-xr-xutils/clangdiag.py4
-rw-r--r--utils/find-unused-diagnostics.sh2
-rw-r--r--utils/hmaptool/CMakeLists.txt16
-rwxr-xr-xutils/hmaptool/hmaptool296
19 files changed, 1893 insertions, 596 deletions
diff --git a/utils/TableGen/ClangAttrEmitter.cpp b/utils/TableGen/ClangAttrEmitter.cpp
index b0e2ddd91362..0bf7a07cf6e1 100644
--- a/utils/TableGen/ClangAttrEmitter.cpp
+++ b/utils/TableGen/ClangAttrEmitter.cpp
@@ -87,6 +87,8 @@ GetFlattenedSpellings(const Record &Attr) {
} else if (Variety == "Clang") {
Ret.emplace_back("GNU", Name, "", false);
Ret.emplace_back("CXX11", Name, "clang", false);
+ if (Spelling->getValueAsBit("AllowInC"))
+ Ret.emplace_back("C2x", Name, "clang", false);
} else
Ret.push_back(FlattenedSpelling(*Spelling));
}
@@ -102,6 +104,7 @@ static std::string ReadPCHRecord(StringRef type) {
.Case("Expr *", "Record.readExpr()")
.Case("IdentifierInfo *", "Record.getIdentifierInfo()")
.Case("StringRef", "Record.readString()")
+ .Case("ParamIdx", "ParamIdx::deserialize(Record.readInt())")
.Default("Record.readInt()");
}
@@ -120,6 +123,7 @@ static std::string WritePCHRecord(StringRef type, StringRef name) {
.Case("Expr *", "AddStmt(" + std::string(name) + ");\n")
.Case("IdentifierInfo *", "AddIdentifierRef(" + std::string(name) + ");\n")
.Case("StringRef", "AddString(" + std::string(name) + ");\n")
+ .Case("ParamIdx", "push_back(" + std::string(name) + ".serialize());\n")
.Default("push_back(" + std::string(name) + ");\n");
}
@@ -229,6 +233,7 @@ namespace {
virtual void writePCHReadArgs(raw_ostream &OS) const = 0;
virtual void writePCHReadDecls(raw_ostream &OS) const = 0;
virtual void writePCHWrite(raw_ostream &OS) const = 0;
+ virtual std::string getIsOmitted() const { return "false"; }
virtual void writeValue(raw_ostream &OS) const = 0;
virtual void writeDump(raw_ostream &OS) const = 0;
virtual void writeDumpChildren(raw_ostream &OS) const {}
@@ -296,23 +301,29 @@ namespace {
std::string(getUpperName()) + "()");
}
+ std::string getIsOmitted() const override {
+ if (type == "IdentifierInfo *")
+ return "!get" + getUpperName().str() + "()";
+ if (type == "ParamIdx")
+ return "!get" + getUpperName().str() + "().isValid()";
+ return "false";
+ }
+
void writeValue(raw_ostream &OS) const override {
- if (type == "FunctionDecl *") {
+ if (type == "FunctionDecl *")
OS << "\" << get" << getUpperName()
<< "()->getNameInfo().getAsString() << \"";
- } else if (type == "IdentifierInfo *") {
- OS << "\";\n";
- if (isOptional())
- OS << " if (get" << getUpperName() << "()) ";
- else
- OS << " ";
- OS << "OS << get" << getUpperName() << "()->getName();\n";
- OS << " OS << \"";
- } else if (type == "TypeSourceInfo *") {
+ else if (type == "IdentifierInfo *")
+ // Some non-optional (comma required) identifier arguments can be the
+ // empty string but are then recorded as a nullptr.
+ OS << "\" << (get" << getUpperName() << "() ? get" << getUpperName()
+ << "()->getName() : \"\") << \"";
+ else if (type == "TypeSourceInfo *")
OS << "\" << get" << getUpperName() << "().getAsString() << \"";
- } else {
+ else if (type == "ParamIdx")
+ OS << "\" << get" << getUpperName() << "().getSourceIndex() << \"";
+ else
OS << "\" << get" << getUpperName() << "() << \"";
- }
}
void writeDump(raw_ostream &OS) const override {
@@ -320,9 +331,10 @@ namespace {
OS << " OS << \" \";\n";
OS << " dumpBareDeclRef(SA->get" << getUpperName() << "());\n";
} else if (type == "IdentifierInfo *") {
- if (isOptional())
- OS << " if (SA->get" << getUpperName() << "())\n ";
- OS << " OS << \" \" << SA->get" << getUpperName()
+ // Some non-optional (comma required) identifier arguments can be the
+ // empty string but are then recorded as a nullptr.
+ OS << " if (SA->get" << getUpperName() << "())\n"
+ << " OS << \" \" << SA->get" << getUpperName()
<< "()->getName();\n";
} else if (type == "TypeSourceInfo *") {
OS << " OS << \" \" << SA->get" << getUpperName()
@@ -332,6 +344,11 @@ namespace {
<< getUpperName() << "\";\n";
} else if (type == "int" || type == "unsigned") {
OS << " OS << \" \" << SA->get" << getUpperName() << "();\n";
+ } else if (type == "ParamIdx") {
+ if (isOptional())
+ OS << " if (SA->get" << getUpperName() << "().isValid())\n ";
+ OS << " OS << \" \" << SA->get" << getUpperName()
+ << "().getSourceIndex();\n";
} else {
llvm_unreachable("Unknown SimpleArgument type!");
}
@@ -574,12 +591,15 @@ namespace {
<< "Type());\n";
}
+ std::string getIsOmitted() const override {
+ return "!is" + getLowerName().str() + "Expr || !" + getLowerName().str()
+ + "Expr";
+ }
+
void writeValue(raw_ostream &OS) const override {
OS << "\";\n";
- // The aligned attribute argument expression is optional.
- OS << " if (is" << getLowerName() << "Expr && "
- << getLowerName() << "Expr)\n";
- OS << " " << getLowerName() << "Expr->printPretty(OS, nullptr, Policy);\n";
+ OS << " " << getLowerName()
+ << "Expr->printPretty(OS, nullptr, Policy);\n";
OS << " OS << \"";
}
@@ -606,6 +626,10 @@ namespace {
virtual void writeValueImpl(raw_ostream &OS) const {
OS << " OS << Val;\n";
}
+ // Assumed to receive a parameter: raw_ostream OS.
+ virtual void writeDumpImpl(raw_ostream &OS) const {
+ OS << " OS << \" \" << Val;\n";
+ }
public:
VariadicArgument(const Record &Arg, StringRef Attr, std::string T)
@@ -732,7 +756,22 @@ namespace {
void writeDump(raw_ostream &OS) const override {
OS << " for (const auto &Val : SA->" << RangeName << "())\n";
- OS << " OS << \" \" << Val;\n";
+ writeDumpImpl(OS);
+ }
+ };
+
+ class VariadicParamIdxArgument : public VariadicArgument {
+ public:
+ VariadicParamIdxArgument(const Record &Arg, StringRef Attr)
+ : VariadicArgument(Arg, Attr, "ParamIdx") {}
+
+ public:
+ void writeValueImpl(raw_ostream &OS) const override {
+ OS << " OS << Val.getSourceIndex();\n";
+ }
+
+ void writeDumpImpl(raw_ostream &OS) const override {
+ OS << " OS << \" \" << Val.getSourceIndex();\n";
}
};
@@ -1134,6 +1173,13 @@ namespace {
}
};
+ class VariadicIdentifierArgument : public VariadicArgument {
+ public:
+ VariadicIdentifierArgument(const Record &Arg, StringRef Attr)
+ : VariadicArgument(Arg, Attr, "IdentifierInfo *")
+ {}
+ };
+
class VariadicStringArgument : public VariadicArgument {
public:
VariadicStringArgument(const Record &Arg, StringRef Attr)
@@ -1235,6 +1281,12 @@ createArgument(const Record &Arg, StringRef Attr,
Ptr = llvm::make_unique<VariadicEnumArgument>(Arg, Attr);
else if (ArgName == "VariadicExprArgument")
Ptr = llvm::make_unique<VariadicExprArgument>(Arg, Attr);
+ else if (ArgName == "VariadicParamIdxArgument")
+ Ptr = llvm::make_unique<VariadicParamIdxArgument>(Arg, Attr);
+ else if (ArgName == "ParamIdxArgument")
+ Ptr = llvm::make_unique<SimpleArgument>(Arg, Attr, "ParamIdx");
+ else if (ArgName == "VariadicIdentifierArgument")
+ Ptr = llvm::make_unique<VariadicIdentifierArgument>(Arg, Attr);
else if (ArgName == "VersionArgument")
Ptr = llvm::make_unique<VersionArgument>(Arg, Attr);
@@ -1366,7 +1418,7 @@ writePrettyPrintFunction(Record &R,
" OS << \"" << Prefix << Spelling;
if (Variety == "Pragma") {
- OS << " \";\n";
+ OS << "\";\n";
OS << " printPrettyPragma(OS, Policy);\n";
OS << " OS << \"\\n\";";
OS << " break;\n";
@@ -1374,33 +1426,83 @@ writePrettyPrintFunction(Record &R,
continue;
}
- // Fake arguments aren't part of the parsed form and should not be
- // pretty-printed.
- bool hasNonFakeArgs = llvm::any_of(
- Args, [](const std::unique_ptr<Argument> &A) { return !A->isFake(); });
-
- // FIXME: always printing the parenthesis isn't the correct behavior for
- // attributes which have optional arguments that were not provided. For
- // instance: __attribute__((aligned)) will be pretty printed as
- // __attribute__((aligned())). The logic should check whether there is only
- // a single argument, and if it is optional, whether it has been provided.
- if (hasNonFakeArgs)
- OS << "(";
if (Spelling == "availability") {
+ OS << "(";
writeAvailabilityValue(OS);
+ OS << ")";
} else if (Spelling == "deprecated" || Spelling == "gnu::deprecated") {
- writeDeprecatedAttrValue(OS, Variety);
+ OS << "(";
+ writeDeprecatedAttrValue(OS, Variety);
+ OS << ")";
} else {
- unsigned index = 0;
+ // To avoid printing parentheses around an empty argument list or
+ // printing spurious commas at the end of an argument list, we need to
+ // determine where the last provided non-fake argument is.
+ unsigned NonFakeArgs = 0;
+ unsigned TrailingOptArgs = 0;
+ bool FoundNonOptArg = false;
+ for (const auto &arg : llvm::reverse(Args)) {
+ if (arg->isFake())
+ continue;
+ ++NonFakeArgs;
+ if (FoundNonOptArg)
+ continue;
+ // FIXME: arg->getIsOmitted() == "false" means we haven't implemented
+ // any way to detect whether the argument was omitted.
+ if (!arg->isOptional() || arg->getIsOmitted() == "false") {
+ FoundNonOptArg = true;
+ continue;
+ }
+ if (!TrailingOptArgs++)
+ OS << "\";\n"
+ << " unsigned TrailingOmittedArgs = 0;\n";
+ OS << " if (" << arg->getIsOmitted() << ")\n"
+ << " ++TrailingOmittedArgs;\n";
+ }
+ if (TrailingOptArgs)
+ OS << " OS << \"";
+ if (TrailingOptArgs < NonFakeArgs)
+ OS << "(";
+ else if (TrailingOptArgs)
+ OS << "\";\n"
+ << " if (TrailingOmittedArgs < " << NonFakeArgs << ")\n"
+ << " OS << \"(\";\n"
+ << " OS << \"";
+ unsigned ArgIndex = 0;
for (const auto &arg : Args) {
- if (arg->isFake()) continue;
- if (index++) OS << ", ";
+ if (arg->isFake())
+ continue;
+ if (ArgIndex) {
+ if (ArgIndex >= NonFakeArgs - TrailingOptArgs)
+ OS << "\";\n"
+ << " if (" << ArgIndex << " < " << NonFakeArgs
+ << " - TrailingOmittedArgs)\n"
+ << " OS << \", \";\n"
+ << " OS << \"";
+ else
+ OS << ", ";
+ }
+ std::string IsOmitted = arg->getIsOmitted();
+ if (arg->isOptional() && IsOmitted != "false")
+ OS << "\";\n"
+ << " if (!(" << IsOmitted << ")) {\n"
+ << " OS << \"";
arg->writeValue(OS);
+ if (arg->isOptional() && IsOmitted != "false")
+ OS << "\";\n"
+ << " }\n"
+ << " OS << \"";
+ ++ArgIndex;
}
+ if (TrailingOptArgs < NonFakeArgs)
+ OS << ")";
+ else if (TrailingOptArgs)
+ OS << "\";\n"
+ << " if (TrailingOmittedArgs < " << NonFakeArgs << ")\n"
+ << " OS << \")\";\n"
+ << " OS << \"";
}
- if (hasNonFakeArgs)
- OS << ")";
OS << Suffix + "\";\n";
OS <<
@@ -1414,7 +1516,7 @@ writePrettyPrintFunction(Record &R,
OS << "}\n\n";
}
-/// \brief Return the index of a spelling in a spelling list.
+/// Return the index of a spelling in a spelling list.
static unsigned
getSpellingListIndex(const std::vector<FlattenedSpelling> &SpellingList,
const FlattenedSpelling &Spelling) {
@@ -1963,7 +2065,7 @@ static void forEachUniqueSpelling(const Record &Attr, Fn &&F) {
}
}
-/// \brief Emits the first-argument-is-type property for attributes.
+/// Emits the first-argument-is-type property for attributes.
static void emitClangAttrTypeArgList(RecordKeeper &Records, raw_ostream &OS) {
OS << "#if defined(CLANG_ATTR_TYPE_ARG_LIST)\n";
std::vector<Record *> Attrs = Records.getAllDerivedDefinitions("Attr");
@@ -1985,7 +2087,7 @@ static void emitClangAttrTypeArgList(RecordKeeper &Records, raw_ostream &OS) {
OS << "#endif // CLANG_ATTR_TYPE_ARG_LIST\n\n";
}
-/// \brief Emits the parse-arguments-in-unevaluated-context property for
+/// Emits the parse-arguments-in-unevaluated-context property for
/// attributes.
static void emitClangAttrArgContextList(RecordKeeper &Records, raw_ostream &OS) {
OS << "#if defined(CLANG_ATTR_ARG_CONTEXT_LIST)\n";
@@ -2013,6 +2115,34 @@ static bool isIdentifierArgument(Record *Arg) {
.Default(false);
}
+static bool isVariadicIdentifierArgument(Record *Arg) {
+ return !Arg->getSuperClasses().empty() &&
+ llvm::StringSwitch<bool>(
+ Arg->getSuperClasses().back().first->getName())
+ .Case("VariadicIdentifierArgument", true)
+ .Default(false);
+}
+
+static void emitClangAttrVariadicIdentifierArgList(RecordKeeper &Records,
+ raw_ostream &OS) {
+ OS << "#if defined(CLANG_ATTR_VARIADIC_IDENTIFIER_ARG_LIST)\n";
+ std::vector<Record *> Attrs = Records.getAllDerivedDefinitions("Attr");
+ for (const auto *A : Attrs) {
+ // Determine whether the first argument is a variadic identifier.
+ std::vector<Record *> Args = A->getValueAsListOfDefs("Args");
+ if (Args.empty() || !isVariadicIdentifierArgument(Args[0]))
+ continue;
+
+ // All these spellings take an identifier argument.
+ forEachUniqueSpelling(*A, [&](const FlattenedSpelling &S) {
+ OS << ".Case(\"" << S.name() << "\", "
+ << "true"
+ << ")\n";
+ });
+ }
+ OS << "#endif // CLANG_ATTR_VARIADIC_IDENTIFIER_ARG_LIST\n\n";
+}
+
// Emits the first-argument-is-identifier property for attributes.
static void emitClangAttrIdentifierArgList(RecordKeeper &Records, raw_ostream &OS) {
OS << "#if defined(CLANG_ATTR_IDENTIFIER_ARG_LIST)\n";
@@ -2063,10 +2193,14 @@ void EmitClangAttrClass(RecordKeeper &Records, raw_ostream &OS) {
ArrayRef<std::pair<Record *, SMRange>> Supers = R.getSuperClasses();
assert(!Supers.empty() && "Forgot to specify a superclass for the attr");
std::string SuperName;
+ bool Inheritable = false;
for (const auto &Super : llvm::reverse(Supers)) {
const Record *R = Super.first;
- if (R->getName() != "TargetSpecificAttr" && SuperName.empty())
+ if (R->getName() != "TargetSpecificAttr" &&
+ R->getName() != "DeclOrTypeAttr" && SuperName.empty())
SuperName = R->getName();
+ if (R->getName() == "InheritableAttr")
+ Inheritable = true;
}
OS << "class " << R.getName() << "Attr : public " << SuperName << " {\n";
@@ -2160,8 +2294,13 @@ void EmitClangAttrClass(RecordKeeper &Records, raw_ostream &OS) {
OS << " )\n";
OS << " : " << SuperName << "(attr::" << R.getName() << ", R, SI, "
- << ( R.getValueAsBit("LateParsed") ? "true" : "false" ) << ", "
- << ( R.getValueAsBit("DuplicatesAllowedWhileMerging") ? "true" : "false" ) << ")\n";
+ << ( R.getValueAsBit("LateParsed") ? "true" : "false" );
+ if (Inheritable) {
+ OS << ", "
+ << (R.getValueAsBit("InheritEvenIfAlreadyPresent") ? "true"
+ : "false");
+ }
+ OS << ")\n";
for (auto const &ai : Args) {
OS << " , ";
@@ -3030,7 +3169,7 @@ static void emitArgInfo(const Record &R, raw_ostream &OS) {
}
static void GenerateDefaultAppertainsTo(raw_ostream &OS) {
- OS << "static bool defaultAppertainsTo(Sema &, const AttributeList &,";
+ OS << "static bool defaultAppertainsTo(Sema &, const ParsedAttr &,";
OS << "const Decl *) {\n";
OS << " return true;\n";
OS << "}\n\n";
@@ -3168,7 +3307,7 @@ static std::string GenerateAppertainsTo(const Record &Attr, raw_ostream &OS) {
// name of that check to the caller.
std::string FnName = "check" + Attr.getName().str() + "AppertainsTo";
std::stringstream SS;
- SS << "static bool " << FnName << "(Sema &S, const AttributeList &Attr, ";
+ SS << "static bool " << FnName << "(Sema &S, const ParsedAttr &Attr, ";
SS << "const Decl *D) {\n";
SS << " if (";
for (auto I = Subjects.begin(), E = Subjects.end(); I != E; ++I) {
@@ -3240,7 +3379,7 @@ emitAttributeMatchRules(PragmaClangAttributeSupport &PragmaAttributeSupport,
static void GenerateDefaultLangOptRequirements(raw_ostream &OS) {
OS << "static bool defaultDiagnoseLangOpts(Sema &, ";
- OS << "const AttributeList &) {\n";
+ OS << "const ParsedAttr &) {\n";
OS << " return true;\n";
OS << "}\n\n";
}
@@ -3279,7 +3418,7 @@ static std::string GenerateLangOptRequirements(const Record &R,
if (I != CustomLangOptsSet.end())
return *I;
- OS << "static bool " << FnName << "(Sema &S, const AttributeList &Attr) {\n";
+ OS << "static bool " << FnName << "(Sema &S, const ParsedAttr &Attr) {\n";
OS << " if (" << Test << ")\n";
OS << " return true;\n\n";
OS << " S.Diag(Attr.getLoc(), diag::warn_attribute_ignored) ";
@@ -3312,7 +3451,7 @@ static std::string GenerateTargetRequirements(const Record &Attr,
// If there are other attributes which share the same parsed attribute kind,
// such as target-specific attributes with a shared spelling, collapse the
// duplicate architectures. This is required because a shared target-specific
- // attribute has only one AttributeList::Kind enumeration value, but it
+ // attribute has only one ParsedAttr::Kind enumeration value, but it
// applies to multiple target architectures. In order for the attribute to be
// considered valid, all of its architectures need to be included.
if (!Attr.isValueUnset("ParseKind")) {
@@ -3349,7 +3488,7 @@ static std::string GenerateTargetRequirements(const Record &Attr,
static void GenerateDefaultSpellingIndexToSemanticSpelling(raw_ostream &OS) {
OS << "static unsigned defaultSpellingIndexToSemanticSpelling("
- << "const AttributeList &Attr) {\n";
+ << "const ParsedAttr &Attr) {\n";
OS << " return UINT_MAX;\n";
OS << "}\n\n";
}
@@ -3372,7 +3511,7 @@ static std::string GenerateSpellingIndexToSemanticSpelling(const Record &Attr,
std::string Enum = CreateSemanticSpellings(Spellings, SemanticToSyntacticMap);
std::string Name = Attr.getName().str() + "AttrSpellingMap";
- OS << "static unsigned " << Name << "(const AttributeList &Attr) {\n";
+ OS << "static unsigned " << Name << "(const ParsedAttr &Attr) {\n";
OS << Enum;
OS << " unsigned Idx = Attr.getAttributeSpellingListIndex();\n";
WriteSemanticSpellingSwitch("Idx", SemanticToSyntacticMap, OS);
@@ -3422,12 +3561,14 @@ void EmitClangAttrParsedAttrImpl(RecordKeeper &Records, raw_ostream &OS) {
// the spellings are identical, and custom parsing rules match, etc.
// We need to generate struct instances based off ParsedAttrInfo from
- // AttributeList.cpp.
+ // ParsedAttr.cpp.
SS << " { ";
emitArgInfo(*I->second, SS);
SS << ", " << I->second->getValueAsBit("HasCustomParsing");
SS << ", " << I->second->isSubClassOf("TargetSpecificAttr");
- SS << ", " << I->second->isSubClassOf("TypeAttr");
+ SS << ", "
+ << (I->second->isSubClassOf("TypeAttr") ||
+ I->second->isSubClassOf("DeclOrTypeAttr"));
SS << ", " << I->second->isSubClassOf("StmtAttr");
SS << ", " << IsKnownToGCC(*I->second);
SS << ", " << PragmaAttributeSupport.isAttributedSupported(*I->second);
@@ -3445,7 +3586,8 @@ void EmitClangAttrParsedAttrImpl(RecordKeeper &Records, raw_ostream &OS) {
SS << " // AT_" << I->first << "\n";
}
- OS << "static const ParsedAttrInfo AttrInfoMap[AttributeList::UnknownAttribute + 1] = {\n";
+ OS << "static const ParsedAttrInfo AttrInfoMap[ParsedAttr::UnknownAttribute "
+ "+ 1] = {\n";
OS << SS.str();
OS << "};\n\n";
@@ -3473,7 +3615,7 @@ void EmitClangAttrParsedAttrKinds(RecordKeeper &Records, raw_ostream &OS) {
// specific attribute, or MSP430-specific attribute. Additionally, an
// attribute can be spelled GNU<"dllexport"> and Declspec<"dllexport">
// for the same semantic attribute. Ultimately, we need to map each of
- // these to a single AttributeList::Kind value, but the StringMatcher
+ // these to a single ParsedAttr::Kind value, but the StringMatcher
// class cannot handle duplicate match strings. So we generate a list of
// string to match based on the syntax, and emit multiple string matchers
// depending on the syntax used.
@@ -3520,34 +3662,34 @@ void EmitClangAttrParsedAttrKinds(RecordKeeper &Records, raw_ostream &OS) {
Spelling += RawSpelling;
if (SemaHandler)
- Matches->push_back(StringMatcher::StringPair(Spelling,
- "return AttributeList::AT_" + AttrName + ";"));
+ Matches->push_back(StringMatcher::StringPair(
+ Spelling, "return ParsedAttr::AT_" + AttrName + ";"));
else
- Matches->push_back(StringMatcher::StringPair(Spelling,
- "return AttributeList::IgnoredAttribute;"));
+ Matches->push_back(StringMatcher::StringPair(
+ Spelling, "return ParsedAttr::IgnoredAttribute;"));
}
}
}
-
- OS << "static AttributeList::Kind getAttrKind(StringRef Name, ";
- OS << "AttributeList::Syntax Syntax) {\n";
- OS << " if (AttributeList::AS_GNU == Syntax) {\n";
+
+ OS << "static ParsedAttr::Kind getAttrKind(StringRef Name, ";
+ OS << "ParsedAttr::Syntax Syntax) {\n";
+ OS << " if (ParsedAttr::AS_GNU == Syntax) {\n";
StringMatcher("Name", GNU, OS).Emit();
- OS << " } else if (AttributeList::AS_Declspec == Syntax) {\n";
+ OS << " } else if (ParsedAttr::AS_Declspec == Syntax) {\n";
StringMatcher("Name", Declspec, OS).Emit();
- OS << " } else if (AttributeList::AS_Microsoft == Syntax) {\n";
+ OS << " } else if (ParsedAttr::AS_Microsoft == Syntax) {\n";
StringMatcher("Name", Microsoft, OS).Emit();
- OS << " } else if (AttributeList::AS_CXX11 == Syntax) {\n";
+ OS << " } else if (ParsedAttr::AS_CXX11 == Syntax) {\n";
StringMatcher("Name", CXX11, OS).Emit();
- OS << " } else if (AttributeList::AS_C2x == Syntax) {\n";
+ OS << " } else if (ParsedAttr::AS_C2x == Syntax) {\n";
StringMatcher("Name", C2x, OS).Emit();
- OS << " } else if (AttributeList::AS_Keyword == Syntax || ";
- OS << "AttributeList::AS_ContextSensitiveKeyword == Syntax) {\n";
+ OS << " } else if (ParsedAttr::AS_Keyword == Syntax || ";
+ OS << "ParsedAttr::AS_ContextSensitiveKeyword == Syntax) {\n";
StringMatcher("Name", Keywords, OS).Emit();
- OS << " } else if (AttributeList::AS_Pragma == Syntax) {\n";
+ OS << " } else if (ParsedAttr::AS_Pragma == Syntax) {\n";
StringMatcher("Name", Pragma, OS).Emit();
OS << " }\n";
- OS << " return AttributeList::UnknownAttribute;\n"
+ OS << " return ParsedAttr::UnknownAttribute;\n"
<< "}\n";
}
@@ -3592,6 +3734,7 @@ void EmitClangAttrParserStringSwitches(RecordKeeper &Records,
emitSourceFileHeader("Parser-related llvm::StringSwitch cases", OS);
emitClangAttrArgContextList(Records, OS);
emitClangAttrIdentifierArgList(Records, OS);
+ emitClangAttrVariadicIdentifierArgList(Records, OS);
emitClangAttrTypeArgList(Records, OS);
emitClangAttrLateParsedList(Records, OS);
}
@@ -3753,8 +3896,8 @@ static void WriteDocumentation(RecordKeeper &Records,
const Record &Deprecated = *Doc.Documentation->getValueAsDef("Deprecated");
const StringRef Replacement = Deprecated.getValueAsString("Replacement");
if (!Replacement.empty())
- OS << " This attribute has been superseded by ``"
- << Replacement << "``.";
+ OS << " This attribute has been superseded by ``" << Replacement
+ << "``.";
OS << "\n\n";
}
@@ -3807,9 +3950,9 @@ void EmitClangAttrDocs(RecordKeeper &Records, raw_ostream &OS) {
for (auto &I : SplitDocs) {
WriteCategoryHeader(I.first, OS);
- std::sort(I.second.begin(), I.second.end(),
- [](const DocumentationData &D1, const DocumentationData &D2) {
- return D1.Heading < D2.Heading;
+ llvm::sort(I.second.begin(), I.second.end(),
+ [](const DocumentationData &D1, const DocumentationData &D2) {
+ return D1.Heading < D2.Heading;
});
// Walk over each of the attributes in the category and write out their
diff --git a/utils/TableGen/ClangCommentHTMLNamedCharacterReferenceEmitter.cpp b/utils/TableGen/ClangCommentHTMLNamedCharacterReferenceEmitter.cpp
index bfdb268b63ba..bea97ae13289 100644
--- a/utils/TableGen/ClangCommentHTMLNamedCharacterReferenceEmitter.cpp
+++ b/utils/TableGen/ClangCommentHTMLNamedCharacterReferenceEmitter.cpp
@@ -22,7 +22,7 @@
using namespace llvm;
-/// \brief Convert a code point to the corresponding UTF-8 sequence represented
+/// Convert a code point to the corresponding UTF-8 sequence represented
/// as a C string literal.
///
/// \returns true on success.
diff --git a/utils/TableGen/ClangDiagnosticsEmitter.cpp b/utils/TableGen/ClangDiagnosticsEmitter.cpp
index d9d99e0bb002..6bfb3f9f61f5 100644
--- a/utils/TableGen/ClangDiagnosticsEmitter.cpp
+++ b/utils/TableGen/ClangDiagnosticsEmitter.cpp
@@ -14,12 +14,13 @@
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/PointerUnion.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringMap.h"
-#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/Twine.h"
+#include "llvm/Support/Casting.h"
#include "llvm/TableGen/Error.h"
#include "llvm/TableGen/Record.h"
#include "llvm/TableGen/StringToOffsetTable.h"
@@ -154,16 +155,7 @@ static bool beforeThanCompareGroups(const GroupInfo *LHS, const GroupInfo *RHS){
RHS->DiagsInGroup.front());
}
-static SMRange findSuperClassRange(const Record *R, StringRef SuperName) {
- ArrayRef<std::pair<Record *, SMRange>> Supers = R->getSuperClasses();
- auto I = std::find_if(Supers.begin(), Supers.end(),
- [&](const std::pair<Record *, SMRange> &SuperPair) {
- return SuperPair.first->getName() == SuperName;
- });
- return (I != Supers.end()) ? I->second : SMRange();
-}
-
-/// \brief Invert the 1-[0/1] mapping of diags to group into a one to many
+/// Invert the 1-[0/1] mapping of diags to group into a one to many
/// mapping of groups to diags in the group.
static void groupDiagnostics(const std::vector<Record*> &Diags,
const std::vector<Record*> &DiagGroups,
@@ -216,9 +208,9 @@ static void groupDiagnostics(const std::vector<Record*> &Diags,
E = SortedGroups.end();
I != E; ++I) {
MutableArrayRef<const Record *> GroupDiags = (*I)->DiagsInGroup;
- std::sort(GroupDiags.begin(), GroupDiags.end(), beforeThanCompare);
+ llvm::sort(GroupDiags.begin(), GroupDiags.end(), beforeThanCompare);
}
- std::sort(SortedGroups.begin(), SortedGroups.end(), beforeThanCompareGroups);
+ llvm::sort(SortedGroups.begin(), SortedGroups.end(), beforeThanCompareGroups);
// Warn about the same group being used anonymously in multiple places.
for (SmallVectorImpl<GroupInfo *>::const_iterator I = SortedGroups.begin(),
@@ -236,22 +228,10 @@ static void groupDiagnostics(const std::vector<Record*> &Diags,
if (NextDiagGroup == (*I)->ExplicitDef)
continue;
- SMRange InGroupRange = findSuperClassRange(*DI, "InGroup");
- SmallString<64> Replacement;
- if (InGroupRange.isValid()) {
- Replacement += "InGroup<";
- Replacement += (*I)->ExplicitDef->getName();
- Replacement += ">";
- }
- SMFixIt FixIt(InGroupRange, Replacement);
-
- SrcMgr.PrintMessage(NextDiagGroup->getLoc().front(),
+ SrcMgr.PrintMessage((*DI)->getLoc().front(),
SourceMgr::DK_Error,
Twine("group '") + Name +
- "' is referred to anonymously",
- None,
- InGroupRange.isValid() ? FixIt
- : ArrayRef<SMFixIt>());
+ "' is referred to anonymously");
SrcMgr.PrintMessage((*I)->ExplicitDef->getLoc().front(),
SourceMgr::DK_Note, "group defined here");
}
@@ -266,19 +246,14 @@ static void groupDiagnostics(const std::vector<Record*> &Diags,
const Record *NextDiagGroup = GroupInit->getDef();
std::string Name = NextDiagGroup->getValueAsString("GroupName");
- SMRange InGroupRange = findSuperClassRange(*DI, "InGroup");
- SrcMgr.PrintMessage(NextDiagGroup->getLoc().front(),
+ SrcMgr.PrintMessage((*DI)->getLoc().front(),
SourceMgr::DK_Error,
Twine("group '") + Name +
- "' is referred to anonymously",
- InGroupRange);
+ "' is referred to anonymously");
for (++DI; DI != DE; ++DI) {
- GroupInit = cast<DefInit>((*DI)->getValueInit("Group"));
- InGroupRange = findSuperClassRange(*DI, "InGroup");
- SrcMgr.PrintMessage(GroupInit->getDef()->getLoc().front(),
- SourceMgr::DK_Note, "also referenced here",
- InGroupRange);
+ SrcMgr.PrintMessage((*DI)->getLoc().front(),
+ SourceMgr::DK_Note, "also referenced here");
}
}
}
@@ -467,6 +442,735 @@ void InferPedantic::compute(VecOrSet DiagsInPedantic,
}
}
+namespace {
+enum PieceKind {
+ MultiPieceClass,
+ TextPieceClass,
+ PlaceholderPieceClass,
+ SelectPieceClass,
+ PluralPieceClass,
+ DiffPieceClass,
+ SubstitutionPieceClass,
+};
+
+enum ModifierType {
+ MT_Unknown,
+ MT_Placeholder,
+ MT_Select,
+ MT_Sub,
+ MT_Plural,
+ MT_Diff,
+ MT_Ordinal,
+ MT_S,
+ MT_Q,
+ MT_ObjCClass,
+ MT_ObjCInstance,
+};
+
+static StringRef getModifierName(ModifierType MT) {
+ switch (MT) {
+ case MT_Select:
+ return "select";
+ case MT_Sub:
+ return "sub";
+ case MT_Diff:
+ return "diff";
+ case MT_Plural:
+ return "plural";
+ case MT_Ordinal:
+ return "ordinal";
+ case MT_S:
+ return "s";
+ case MT_Q:
+ return "q";
+ case MT_Placeholder:
+ return "";
+ case MT_ObjCClass:
+ return "objcclass";
+ case MT_ObjCInstance:
+ return "objcinstance";
+ case MT_Unknown:
+ llvm_unreachable("invalid modifier type");
+ }
+ // Unhandled case
+ llvm_unreachable("invalid modifier type");
+}
+
+struct Piece {
+ // This type and its derived classes are move-only.
+ Piece(PieceKind Kind) : ClassKind(Kind) {}
+ Piece(Piece const &O) = delete;
+ Piece &operator=(Piece const &) = delete;
+ virtual ~Piece() {}
+
+ PieceKind getPieceClass() const { return ClassKind; }
+ static bool classof(const Piece *) { return true; }
+
+private:
+ PieceKind ClassKind;
+};
+
+struct MultiPiece : Piece {
+ MultiPiece() : Piece(MultiPieceClass) {}
+ MultiPiece(std::vector<Piece *> Pieces)
+ : Piece(MultiPieceClass), Pieces(std::move(Pieces)) {}
+
+ std::vector<Piece *> Pieces;
+
+ static bool classof(const Piece *P) {
+ return P->getPieceClass() == MultiPieceClass;
+ }
+};
+
+struct TextPiece : Piece {
+ StringRef Role;
+ std::string Text;
+ TextPiece(StringRef Text, StringRef Role = "")
+ : Piece(TextPieceClass), Role(Role), Text(Text.str()) {}
+
+ static bool classof(const Piece *P) {
+ return P->getPieceClass() == TextPieceClass;
+ }
+};
+
+struct PlaceholderPiece : Piece {
+ ModifierType Kind;
+ int Index;
+ PlaceholderPiece(ModifierType Kind, int Index)
+ : Piece(PlaceholderPieceClass), Kind(Kind), Index(Index) {}
+
+ static bool classof(const Piece *P) {
+ return P->getPieceClass() == PlaceholderPieceClass;
+ }
+};
+
+struct SelectPiece : Piece {
+protected:
+ SelectPiece(PieceKind Kind, ModifierType ModKind)
+ : Piece(Kind), ModKind(ModKind) {}
+
+public:
+ SelectPiece(ModifierType ModKind) : SelectPiece(SelectPieceClass, ModKind) {}
+
+ ModifierType ModKind;
+ std::vector<Piece *> Options;
+ int Index;
+
+ static bool classof(const Piece *P) {
+ return P->getPieceClass() == SelectPieceClass ||
+ P->getPieceClass() == PluralPieceClass;
+ }
+};
+
+struct PluralPiece : SelectPiece {
+ PluralPiece() : SelectPiece(PluralPieceClass, MT_Plural) {}
+
+ std::vector<Piece *> OptionPrefixes;
+ int Index;
+
+ static bool classof(const Piece *P) {
+ return P->getPieceClass() == PluralPieceClass;
+ }
+};
+
+struct DiffPiece : Piece {
+ DiffPiece() : Piece(DiffPieceClass) {}
+
+ Piece *Options[2] = {};
+ int Indexes[2] = {};
+
+ static bool classof(const Piece *P) {
+ return P->getPieceClass() == DiffPieceClass;
+ }
+};
+
+struct SubstitutionPiece : Piece {
+ SubstitutionPiece() : Piece(SubstitutionPieceClass) {}
+
+ std::string Name;
+ std::vector<int> Modifiers;
+
+ static bool classof(const Piece *P) {
+ return P->getPieceClass() == SubstitutionPieceClass;
+ }
+};
+
+/// Diagnostic text, parsed into pieces.
+
+
+struct DiagnosticTextBuilder {
+ DiagnosticTextBuilder(DiagnosticTextBuilder const &) = delete;
+ DiagnosticTextBuilder &operator=(DiagnosticTextBuilder const &) = delete;
+
+ DiagnosticTextBuilder(RecordKeeper &Records) {
+ // Build up the list of substitution records.
+ for (auto *S : Records.getAllDerivedDefinitions("TextSubstitution")) {
+ EvaluatingRecordGuard Guard(&EvaluatingRecord, S);
+ Substitutions.try_emplace(
+ S->getName(), DiagText(*this, S->getValueAsString("Substitution")));
+ }
+
+ // Check that no diagnostic definitions have the same name as a
+ // substitution.
+ for (Record *Diag : Records.getAllDerivedDefinitions("Diagnostic")) {
+ StringRef Name = Diag->getName();
+ if (Substitutions.count(Name))
+ llvm::PrintFatalError(
+ Diag->getLoc(),
+ "Diagnostic '" + Name +
+ "' has same name as TextSubstitution definition");
+ }
+ }
+
+ std::vector<std::string> buildForDocumentation(StringRef Role,
+ const Record *R);
+ std::string buildForDefinition(const Record *R);
+
+ Piece *getSubstitution(SubstitutionPiece *S) const {
+ auto It = Substitutions.find(S->Name);
+ if (It == Substitutions.end())
+ PrintFatalError("Failed to find substitution with name: " + S->Name);
+ return It->second.Root;
+ }
+
+ LLVM_ATTRIBUTE_NORETURN void PrintFatalError(llvm::Twine const &Msg) const {
+ assert(EvaluatingRecord && "not evaluating a record?");
+ llvm::PrintFatalError(EvaluatingRecord->getLoc(), Msg);
+ }
+
+private:
+ struct DiagText {
+ DiagnosticTextBuilder &Builder;
+ std::vector<Piece *> AllocatedPieces;
+ Piece *Root = nullptr;
+
+ template <class T, class... Args> T *New(Args &&... args) {
+ static_assert(std::is_base_of<Piece, T>::value, "must be piece");
+ T *Mem = new T(std::forward<Args>(args)...);
+ AllocatedPieces.push_back(Mem);
+ return Mem;
+ }
+
+ DiagText(DiagnosticTextBuilder &Builder, StringRef Text)
+ : Builder(Builder), Root(parseDiagText(Text)) {}
+
+ Piece *parseDiagText(StringRef &Text, bool Nested = false);
+ int parseModifier(StringRef &) const;
+
+ public:
+ DiagText(DiagText &&O) noexcept
+ : Builder(O.Builder), AllocatedPieces(std::move(O.AllocatedPieces)),
+ Root(O.Root) {
+ O.Root = nullptr;
+ }
+
+ ~DiagText() {
+ for (Piece *P : AllocatedPieces)
+ delete P;
+ }
+ };
+
+private:
+ const Record *EvaluatingRecord = nullptr;
+ struct EvaluatingRecordGuard {
+ EvaluatingRecordGuard(const Record **Dest, const Record *New)
+ : Dest(Dest), Old(*Dest) {
+ *Dest = New;
+ }
+ ~EvaluatingRecordGuard() { *Dest = Old; }
+ const Record **Dest;
+ const Record *Old;
+ };
+
+ StringMap<DiagText> Substitutions;
+};
+
+template <class Derived> struct DiagTextVisitor {
+ using ModifierMappingsType = Optional<std::vector<int>>;
+
+private:
+ Derived &getDerived() { return static_cast<Derived &>(*this); }
+
+public:
+ std::vector<int>
+ getSubstitutionMappings(SubstitutionPiece *P,
+ const ModifierMappingsType &Mappings) const {
+ std::vector<int> NewMappings;
+ for (int Idx : P->Modifiers)
+ NewMappings.push_back(mapIndex(Idx, Mappings));
+ return NewMappings;
+ }
+
+ struct SubstitutionContext {
+ SubstitutionContext(DiagTextVisitor &Visitor, SubstitutionPiece *P)
+ : Visitor(Visitor) {
+ Substitution = Visitor.Builder.getSubstitution(P);
+ OldMappings = std::move(Visitor.ModifierMappings);
+ std::vector<int> NewMappings =
+ Visitor.getSubstitutionMappings(P, OldMappings);
+ Visitor.ModifierMappings = std::move(NewMappings);
+ }
+
+ ~SubstitutionContext() {
+ Visitor.ModifierMappings = std::move(OldMappings);
+ }
+
+ private:
+ DiagTextVisitor &Visitor;
+ Optional<std::vector<int>> OldMappings;
+
+ public:
+ Piece *Substitution;
+ };
+
+public:
+ DiagTextVisitor(DiagnosticTextBuilder &Builder) : Builder(Builder) {}
+
+ void Visit(Piece *P) {
+ switch (P->getPieceClass()) {
+#define CASE(T) \
+ case T##PieceClass: \
+ return getDerived().Visit##T(static_cast<T##Piece *>(P))
+ CASE(Multi);
+ CASE(Text);
+ CASE(Placeholder);
+ CASE(Select);
+ CASE(Plural);
+ CASE(Diff);
+ CASE(Substitution);
+#undef CASE
+ }
+ }
+
+ void VisitSubstitution(SubstitutionPiece *P) {
+ SubstitutionContext Guard(*this, P);
+ Visit(Guard.Substitution);
+ }
+
+ int mapIndex(int Idx,
+ ModifierMappingsType const &ModifierMappings) const {
+ if (!ModifierMappings)
+ return Idx;
+ if (ModifierMappings->size() <= static_cast<unsigned>(Idx))
+ Builder.PrintFatalError("Modifier value '" + std::to_string(Idx) +
+ "' is not valid for this mapping (has " +
+ std::to_string(ModifierMappings->size()) +
+ " mappings)");
+ return (*ModifierMappings)[Idx];
+ }
+
+ int mapIndex(int Idx) const {
+ return mapIndex(Idx, ModifierMappings);
+ }
+
+protected:
+ DiagnosticTextBuilder &Builder;
+ ModifierMappingsType ModifierMappings;
+};
+
+void escapeRST(StringRef Str, std::string &Out) {
+ for (auto K : Str) {
+ if (StringRef("`*|_[]\\").count(K))
+ Out.push_back('\\');
+ Out.push_back(K);
+ }
+}
+
+template <typename It> void padToSameLength(It Begin, It End) {
+ size_t Width = 0;
+ for (It I = Begin; I != End; ++I)
+ Width = std::max(Width, I->size());
+ for (It I = Begin; I != End; ++I)
+ (*I) += std::string(Width - I->size(), ' ');
+}
+
+template <typename It> void makeTableRows(It Begin, It End) {
+ if (Begin == End)
+ return;
+ padToSameLength(Begin, End);
+ for (It I = Begin; I != End; ++I)
+ *I = "|" + *I + "|";
+}
+
+void makeRowSeparator(std::string &Str) {
+ for (char &K : Str)
+ K = (K == '|' ? '+' : '-');
+}
+
+struct DiagTextDocPrinter : DiagTextVisitor<DiagTextDocPrinter> {
+ using BaseTy = DiagTextVisitor<DiagTextDocPrinter>;
+ DiagTextDocPrinter(DiagnosticTextBuilder &Builder,
+ std::vector<std::string> &RST)
+ : BaseTy(Builder), RST(RST) {}
+
+ void gatherNodes(
+ Piece *OrigP, const ModifierMappingsType &CurrentMappings,
+ std::vector<std::pair<Piece *, ModifierMappingsType>> &Pieces) const {
+ if (auto *Sub = dyn_cast<SubstitutionPiece>(OrigP)) {
+ ModifierMappingsType NewMappings =
+ getSubstitutionMappings(Sub, CurrentMappings);
+ return gatherNodes(Builder.getSubstitution(Sub), NewMappings, Pieces);
+ }
+ if (auto *MD = dyn_cast<MultiPiece>(OrigP)) {
+ for (Piece *Node : MD->Pieces)
+ gatherNodes(Node, CurrentMappings, Pieces);
+ return;
+ }
+ Pieces.push_back(std::make_pair(OrigP, CurrentMappings));
+ }
+
+ void VisitMulti(MultiPiece *P) {
+ if (P->Pieces.empty()) {
+ RST.push_back("");
+ return;
+ }
+
+ if (P->Pieces.size() == 1)
+ return Visit(P->Pieces[0]);
+
+ // Flatten the list of nodes, replacing any substitution pieces with the
+ // recursively flattened substituted node.
+ std::vector<std::pair<Piece *, ModifierMappingsType>> Pieces;
+ gatherNodes(P, ModifierMappings, Pieces);
+
+ std::string EmptyLinePrefix;
+ size_t Start = RST.size();
+ bool HasMultipleLines = true;
+ for (const std::pair<Piece *, ModifierMappingsType> &NodePair : Pieces) {
+ std::vector<std::string> Lines;
+ DiagTextDocPrinter Visitor{Builder, Lines};
+ Visitor.ModifierMappings = NodePair.second;
+ Visitor.Visit(NodePair.first);
+
+ if (Lines.empty())
+ continue;
+
+ // We need a vertical separator if either this or the previous piece is a
+ // multi-line piece, or this is the last piece.
+ const char *Separator = (Lines.size() > 1 || HasMultipleLines) ? "|" : "";
+ HasMultipleLines = Lines.size() > 1;
+
+ if (Start + Lines.size() > RST.size())
+ RST.resize(Start + Lines.size(), EmptyLinePrefix);
+
+ padToSameLength(Lines.begin(), Lines.end());
+ for (size_t I = 0; I != Lines.size(); ++I)
+ RST[Start + I] += Separator + Lines[I];
+ std::string Empty(Lines[0].size(), ' ');
+ for (size_t I = Start + Lines.size(); I != RST.size(); ++I)
+ RST[I] += Separator + Empty;
+ EmptyLinePrefix += Separator + Empty;
+ }
+ for (size_t I = Start; I != RST.size(); ++I)
+ RST[I] += "|";
+ EmptyLinePrefix += "|";
+
+ makeRowSeparator(EmptyLinePrefix);
+ RST.insert(RST.begin() + Start, EmptyLinePrefix);
+ RST.insert(RST.end(), EmptyLinePrefix);
+ }
+
+ void VisitText(TextPiece *P) {
+ RST.push_back("");
+ auto &S = RST.back();
+
+ StringRef T = P->Text;
+ while (!T.empty() && T.front() == ' ') {
+ RST.back() += " |nbsp| ";
+ T = T.drop_front();
+ }
+
+ std::string Suffix;
+ while (!T.empty() && T.back() == ' ') {
+ Suffix += " |nbsp| ";
+ T = T.drop_back();
+ }
+
+ if (!T.empty()) {
+ S += ':';
+ S += P->Role;
+ S += ":`";
+ escapeRST(T, S);
+ S += '`';
+ }
+
+ S += Suffix;
+ }
+
+ void VisitPlaceholder(PlaceholderPiece *P) {
+ RST.push_back(std::string(":placeholder:`") +
+ char('A' + mapIndex(P->Index)) + "`");
+ }
+
+ void VisitSelect(SelectPiece *P) {
+ std::vector<size_t> SeparatorIndexes;
+ SeparatorIndexes.push_back(RST.size());
+ RST.emplace_back();
+ for (auto *O : P->Options) {
+ Visit(O);
+ SeparatorIndexes.push_back(RST.size());
+ RST.emplace_back();
+ }
+
+ makeTableRows(RST.begin() + SeparatorIndexes.front(),
+ RST.begin() + SeparatorIndexes.back() + 1);
+ for (size_t I : SeparatorIndexes)
+ makeRowSeparator(RST[I]);
+ }
+
+ void VisitPlural(PluralPiece *P) { VisitSelect(P); }
+
+ void VisitDiff(DiffPiece *P) { Visit(P->Options[1]); }
+
+ std::vector<std::string> &RST;
+};
+
+struct DiagTextPrinter : DiagTextVisitor<DiagTextPrinter> {
+public:
+ using BaseTy = DiagTextVisitor<DiagTextPrinter>;
+ DiagTextPrinter(DiagnosticTextBuilder &Builder, std::string &Result)
+ : BaseTy(Builder), Result(Result) {}
+
+ void VisitMulti(MultiPiece *P) {
+ for (auto *Child : P->Pieces)
+ Visit(Child);
+ }
+ void VisitText(TextPiece *P) { Result += P->Text; }
+ void VisitPlaceholder(PlaceholderPiece *P) {
+ Result += "%";
+ Result += getModifierName(P->Kind);
+ addInt(mapIndex(P->Index));
+ }
+ void VisitSelect(SelectPiece *P) {
+ Result += "%";
+ Result += getModifierName(P->ModKind);
+ if (P->ModKind == MT_Select) {
+ Result += "{";
+ for (auto *D : P->Options) {
+ Visit(D);
+ Result += '|';
+ }
+ if (!P->Options.empty())
+ Result.erase(--Result.end());
+ Result += '}';
+ }
+ addInt(mapIndex(P->Index));
+ }
+
+ void VisitPlural(PluralPiece *P) {
+ Result += "%plural{";
+ assert(P->Options.size() == P->OptionPrefixes.size());
+ for (unsigned I = 0, End = P->Options.size(); I < End; ++I) {
+ if (P->OptionPrefixes[I])
+ Visit(P->OptionPrefixes[I]);
+ Visit(P->Options[I]);
+ Result += "|";
+ }
+ if (!P->Options.empty())
+ Result.erase(--Result.end());
+ Result += '}';
+ addInt(mapIndex(P->Index));
+ }
+
+ void VisitDiff(DiffPiece *P) {
+ Result += "%diff{";
+ Visit(P->Options[0]);
+ Result += "|";
+ Visit(P->Options[1]);
+ Result += "}";
+ addInt(mapIndex(P->Indexes[0]));
+ Result += ",";
+ addInt(mapIndex(P->Indexes[1]));
+ }
+
+ void addInt(int Val) { Result += std::to_string(Val); }
+
+ std::string &Result;
+};
+
+int DiagnosticTextBuilder::DiagText::parseModifier(StringRef &Text) const {
+ if (Text.empty() || !isdigit(Text[0]))
+ Builder.PrintFatalError("expected modifier in diagnostic");
+ int Val = 0;
+ do {
+ Val *= 10;
+ Val += Text[0] - '0';
+ Text = Text.drop_front();
+ } while (!Text.empty() && isdigit(Text[0]));
+ return Val;
+}
+
+Piece *DiagnosticTextBuilder::DiagText::parseDiagText(StringRef &Text,
+ bool Nested) {
+ std::vector<Piece *> Parsed;
+
+ while (!Text.empty()) {
+ size_t End = (size_t)-2;
+ do
+ End = Nested ? Text.find_first_of("%|}", End + 2)
+ : Text.find_first_of('%', End + 2);
+ while (End < Text.size() - 1 && Text[End] == '%' &&
+ (Text[End + 1] == '%' || Text[End + 1] == '|'));
+
+ if (End) {
+ Parsed.push_back(New<TextPiece>(Text.slice(0, End), "diagtext"));
+ Text = Text.slice(End, StringRef::npos);
+ if (Text.empty())
+ break;
+ }
+
+ if (Text[0] == '|' || Text[0] == '}')
+ break;
+
+ // Drop the '%'.
+ Text = Text.drop_front();
+
+ // Extract the (optional) modifier.
+ size_t ModLength = Text.find_first_of("0123456789{");
+ StringRef Modifier = Text.slice(0, ModLength);
+ Text = Text.slice(ModLength, StringRef::npos);
+ ModifierType ModType = llvm::StringSwitch<ModifierType>{Modifier}
+ .Case("select", MT_Select)
+ .Case("sub", MT_Sub)
+ .Case("diff", MT_Diff)
+ .Case("plural", MT_Plural)
+ .Case("s", MT_S)
+ .Case("ordinal", MT_Ordinal)
+ .Case("q", MT_Q)
+ .Case("objcclass", MT_ObjCClass)
+ .Case("objcinstance", MT_ObjCInstance)
+ .Case("", MT_Placeholder)
+ .Default(MT_Unknown);
+
+ switch (ModType) {
+ case MT_Unknown:
+ Builder.PrintFatalError("Unknown modifier type: " + Modifier);
+ case MT_Select: {
+ SelectPiece *Select = New<SelectPiece>(MT_Select);
+ do {
+ Text = Text.drop_front(); // '{' or '|'
+ Select->Options.push_back(parseDiagText(Text, true));
+ assert(!Text.empty() && "malformed %select");
+ } while (Text.front() == '|');
+ // Drop the trailing '}'.
+ Text = Text.drop_front(1);
+ Select->Index = parseModifier(Text);
+ Parsed.push_back(Select);
+ continue;
+ }
+ case MT_Plural: {
+ PluralPiece *Plural = New<PluralPiece>();
+ do {
+ Text = Text.drop_front(); // '{' or '|'
+ size_t End = Text.find_first_of(":");
+ if (End == StringRef::npos)
+ Builder.PrintFatalError("expected ':' while parsing %plural");
+ ++End;
+ assert(!Text.empty());
+ Plural->OptionPrefixes.push_back(
+ New<TextPiece>(Text.slice(0, End), "diagtext"));
+ Text = Text.slice(End, StringRef::npos);
+ Plural->Options.push_back(parseDiagText(Text, true));
+ assert(!Text.empty() && "malformed %select");
+ } while (Text.front() == '|');
+ // Drop the trailing '}'.
+ Text = Text.drop_front(1);
+ Plural->Index = parseModifier(Text);
+ Parsed.push_back(Plural);
+ continue;
+ }
+ case MT_Sub: {
+ SubstitutionPiece *Sub = New<SubstitutionPiece>();
+ Text = Text.drop_front(); // '{'
+ size_t NameSize = Text.find_first_of('}');
+ assert(NameSize != size_t(-1) && "failed to find the end of the name");
+ assert(NameSize != 0 && "empty name?");
+ Sub->Name = Text.substr(0, NameSize).str();
+ Text = Text.drop_front(NameSize);
+ Text = Text.drop_front(); // '}'
+ if (!Text.empty()) {
+ while (true) {
+ if (!isdigit(Text[0]))
+ break;
+ Sub->Modifiers.push_back(parseModifier(Text));
+ if (Text.empty() || Text[0] != ',')
+ break;
+ Text = Text.drop_front(); // ','
+ assert(!Text.empty() && isdigit(Text[0]) &&
+ "expected another modifier");
+ }
+ }
+ Parsed.push_back(Sub);
+ continue;
+ }
+ case MT_Diff: {
+ DiffPiece *Diff = New<DiffPiece>();
+ Text = Text.drop_front(); // '{'
+ Diff->Options[0] = parseDiagText(Text, true);
+ Text = Text.drop_front(); // '|'
+ Diff->Options[1] = parseDiagText(Text, true);
+
+ Text = Text.drop_front(); // '}'
+ Diff->Indexes[0] = parseModifier(Text);
+ Text = Text.drop_front(); // ','
+ Diff->Indexes[1] = parseModifier(Text);
+ Parsed.push_back(Diff);
+ continue;
+ }
+ case MT_S: {
+ SelectPiece *Select = New<SelectPiece>(ModType);
+ Select->Options.push_back(New<TextPiece>(""));
+ Select->Options.push_back(New<TextPiece>("s", "diagtext"));
+ Select->Index = parseModifier(Text);
+ Parsed.push_back(Select);
+ continue;
+ }
+ case MT_Q:
+ case MT_Placeholder:
+ case MT_ObjCClass:
+ case MT_ObjCInstance:
+ case MT_Ordinal: {
+ Parsed.push_back(New<PlaceholderPiece>(ModType, parseModifier(Text)));
+ continue;
+ }
+ }
+ }
+
+ return New<MultiPiece>(Parsed);
+}
+
+std::vector<std::string>
+DiagnosticTextBuilder::buildForDocumentation(StringRef Severity,
+ const Record *R) {
+ EvaluatingRecordGuard Guard(&EvaluatingRecord, R);
+ StringRef Text = R->getValueAsString("Text");
+
+ DiagText D(*this, Text);
+ TextPiece *Prefix = D.New<TextPiece>(Severity, Severity);
+ Prefix->Text += ": ";
+ auto *MP = dyn_cast<MultiPiece>(D.Root);
+ if (!MP) {
+ MP = D.New<MultiPiece>();
+ MP->Pieces.push_back(D.Root);
+ D.Root = MP;
+ }
+ MP->Pieces.insert(MP->Pieces.begin(), Prefix);
+ std::vector<std::string> Result;
+ DiagTextDocPrinter{*this, Result}.Visit(D.Root);
+ return Result;
+}
+
+std::string DiagnosticTextBuilder::buildForDefinition(const Record *R) {
+ EvaluatingRecordGuard Guard(&EvaluatingRecord, R);
+ StringRef Text = R->getValueAsString("Text");
+ DiagText D(*this, Text);
+ std::string Result;
+ DiagTextPrinter{*this, Result}.Visit(D.Root);
+ return Result;
+}
+
+} // namespace
+
//===----------------------------------------------------------------------===//
// Warning Tables (.inc file) generation.
//===----------------------------------------------------------------------===//
@@ -481,6 +1185,7 @@ static bool isRemark(const Record &Diag) {
return ClsName == "CLASS_REMARK";
}
+
/// ClangDiagsDefsEmitter - The top-level class emits .def files containing
/// declarations of Clang diagnostics.
namespace clang {
@@ -496,8 +1201,9 @@ void EmitClangDiagsDefs(RecordKeeper &Records, raw_ostream &OS,
OS << "#endif\n\n";
}
- const std::vector<Record*> &Diags =
- Records.getAllDerivedDefinitions("Diagnostic");
+ DiagnosticTextBuilder DiagTextBuilder(Records);
+
+ std::vector<Record *> Diags = Records.getAllDerivedDefinitions("Diagnostic");
std::vector<Record*> DiagGroups
= Records.getAllDerivedDefinitions("DiagGroup");
@@ -546,7 +1252,7 @@ void EmitClangDiagsDefs(RecordKeeper &Records, raw_ostream &OS,
// Description string.
OS << ", \"";
- OS.write_escaped(R.getValueAsString("Text")) << '"';
+ OS.write_escaped(DiagTextBuilder.buildForDefinition(&R)) << '"';
// Warning associated with the diagnostic. This is stored as an index into
// the alphabetically sorted warning table.
@@ -598,7 +1304,7 @@ static std::string getDiagCategoryEnum(llvm::StringRef name) {
return enumName.str();
}
-/// \brief Emit the array of diagnostic subgroups.
+/// Emit the array of diagnostic subgroups.
///
/// The array of diagnostic subgroups contains for each group a list of its
/// subgroups. The individual lists are separated by '-1'. Groups with no
@@ -645,7 +1351,7 @@ static void emitDiagSubGroups(std::map<std::string, GroupInfo> &DiagsInGroup,
OS << "};\n\n";
}
-/// \brief Emit the list of diagnostic arrays.
+/// Emit the list of diagnostic arrays.
///
/// This data structure is a large array that contains itself arrays of varying
/// size. Each array represents a list of diagnostics. The different arrays are
@@ -686,7 +1392,7 @@ static void emitDiagArrays(std::map<std::string, GroupInfo> &DiagsInGroup,
OS << "};\n\n";
}
-/// \brief Emit a list of group names.
+/// Emit a list of group names.
///
/// This creates a long string which by itself contains a list of pascal style
/// strings, which consist of a length byte directly followed by the string.
@@ -703,7 +1409,7 @@ static void emitDiagGroupNames(StringToOffsetTable &GroupNames,
OS << "};\n\n";
}
-/// \brief Emit diagnostic arrays and related data structures.
+/// Emit diagnostic arrays and related data structures.
///
/// This creates the actual diagnostic array, an array of diagnostic subgroups
/// and an array of subgroup names.
@@ -727,7 +1433,7 @@ static void emitAllDiagArrays(std::map<std::string, GroupInfo> &DiagsInGroup,
OS << "#endif // GET_DIAG_ARRAYS\n\n";
}
-/// \brief Emit diagnostic table.
+/// Emit diagnostic table.
///
/// The table is sorted by the name of the diagnostic group. Each element
/// consists of the name of the diagnostic group (given as offset in the
@@ -802,7 +1508,7 @@ static void emitDiagTable(std::map<std::string, GroupInfo> &DiagsInGroup,
OS << "#endif // GET_DIAG_TABLE\n\n";
}
-/// \brief Emit the table of diagnostic categories.
+/// Emit the table of diagnostic categories.
///
/// The table has the form of macro calls that have two parameters. The
/// category's name as well as an enum that represents the category. The
@@ -889,9 +1595,10 @@ void EmitClangDiagsIndexName(RecordKeeper &Records, raw_ostream &OS) {
Index.push_back(RecordIndexElement(R));
}
- std::sort(Index.begin(), Index.end(),
- [](const RecordIndexElement &Lhs,
- const RecordIndexElement &Rhs) { return Lhs.Name < Rhs.Name; });
+ llvm::sort(Index.begin(), Index.end(),
+ [](const RecordIndexElement &Lhs, const RecordIndexElement &Rhs) {
+ return Lhs.Name < Rhs.Name;
+ });
for (unsigned i = 0, e = Index.size(); i != e; ++i) {
const RecordIndexElement &R = Index[i];
@@ -907,261 +1614,6 @@ void EmitClangDiagsIndexName(RecordKeeper &Records, raw_ostream &OS) {
namespace docs {
namespace {
-/// Diagnostic text, parsed into pieces.
-struct DiagText {
- struct Piece {
- // This type and its derived classes are move-only.
- Piece() {}
- Piece(Piece &&O) {}
- Piece &operator=(Piece &&O) { return *this; }
-
- virtual void print(std::vector<std::string> &RST) = 0;
- virtual ~Piece() {}
- };
- struct TextPiece : Piece {
- StringRef Role;
- std::string Text;
- void print(std::vector<std::string> &RST) override;
- };
- struct PlaceholderPiece : Piece {
- int Index;
- void print(std::vector<std::string> &RST) override;
- };
- struct SelectPiece : Piece {
- SelectPiece() {}
- SelectPiece(SelectPiece &&O) noexcept : Options(std::move(O.Options)) {}
- std::vector<DiagText> Options;
- void print(std::vector<std::string> &RST) override;
- };
-
- std::vector<std::unique_ptr<Piece>> Pieces;
-
- DiagText();
- DiagText(DiagText &&O) noexcept : Pieces(std::move(O.Pieces)) {}
-
- DiagText(StringRef Text);
- DiagText(StringRef Kind, StringRef Text);
-
- template<typename P> void add(P Piece) {
- Pieces.push_back(llvm::make_unique<P>(std::move(Piece)));
- }
- void print(std::vector<std::string> &RST);
-};
-
-DiagText parseDiagText(StringRef &Text, bool Nested = false) {
- DiagText Parsed;
-
- while (!Text.empty()) {
- size_t End = (size_t)-2;
- do
- End = Nested ? Text.find_first_of("%|}", End + 2)
- : Text.find_first_of('%', End + 2);
- while (End < Text.size() - 1 && Text[End] == '%' && Text[End + 1] == '%');
-
- if (End) {
- DiagText::TextPiece Piece;
- Piece.Role = "diagtext";
- Piece.Text = Text.slice(0, End);
- Parsed.add(std::move(Piece));
- Text = Text.slice(End, StringRef::npos);
- if (Text.empty()) break;
- }
-
- if (Text[0] == '|' || Text[0] == '}')
- break;
-
- // Drop the '%'.
- Text = Text.drop_front();
-
- // Extract the (optional) modifier.
- size_t ModLength = Text.find_first_of("0123456789{");
- StringRef Modifier = Text.slice(0, ModLength);
- Text = Text.slice(ModLength, StringRef::npos);
-
- // FIXME: Handle %ordinal here.
- if (Modifier == "select" || Modifier == "plural") {
- DiagText::SelectPiece Select;
- do {
- Text = Text.drop_front();
- if (Modifier == "plural")
- while (Text[0] != ':')
- Text = Text.drop_front();
- Select.Options.push_back(parseDiagText(Text, true));
- assert(!Text.empty() && "malformed %select");
- } while (Text.front() == '|');
- Parsed.add(std::move(Select));
-
- // Drop the trailing '}n'.
- Text = Text.drop_front(2);
- continue;
- }
-
- // For %diff, just take the second alternative (tree diagnostic). It would
- // be preferable to take the first one, and replace the $ with the suitable
- // placeholders.
- if (Modifier == "diff") {
- Text = Text.drop_front(); // '{'
- parseDiagText(Text, true);
- Text = Text.drop_front(); // '|'
-
- DiagText D = parseDiagText(Text, true);
- for (auto &P : D.Pieces)
- Parsed.Pieces.push_back(std::move(P));
-
- Text = Text.drop_front(4); // '}n,m'
- continue;
- }
-
- if (Modifier == "s") {
- Text = Text.drop_front();
- DiagText::SelectPiece Select;
- Select.Options.push_back(DiagText(""));
- Select.Options.push_back(DiagText("s"));
- Parsed.add(std::move(Select));
- continue;
- }
-
- assert(!Text.empty() && isdigit(Text[0]) && "malformed placeholder");
- DiagText::PlaceholderPiece Placeholder;
- Placeholder.Index = Text[0] - '0';
- Parsed.add(std::move(Placeholder));
- Text = Text.drop_front();
- continue;
- }
- return Parsed;
-}
-
-DiagText::DiagText() {}
-
-DiagText::DiagText(StringRef Text) : DiagText(parseDiagText(Text, false)) {}
-
-DiagText::DiagText(StringRef Kind, StringRef Text) : DiagText(parseDiagText(Text, false)) {
- TextPiece Prefix;
- Prefix.Role = Kind;
- Prefix.Text = Kind;
- Prefix.Text += ": ";
- Pieces.insert(Pieces.begin(),
- llvm::make_unique<TextPiece>(std::move(Prefix)));
-}
-
-void escapeRST(StringRef Str, std::string &Out) {
- for (auto K : Str) {
- if (StringRef("`*|_[]\\").count(K))
- Out.push_back('\\');
- Out.push_back(K);
- }
-}
-
-template<typename It> void padToSameLength(It Begin, It End) {
- size_t Width = 0;
- for (It I = Begin; I != End; ++I)
- Width = std::max(Width, I->size());
- for (It I = Begin; I != End; ++I)
- (*I) += std::string(Width - I->size(), ' ');
-}
-
-template<typename It> void makeTableRows(It Begin, It End) {
- if (Begin == End) return;
- padToSameLength(Begin, End);
- for (It I = Begin; I != End; ++I)
- *I = "|" + *I + "|";
-}
-
-void makeRowSeparator(std::string &Str) {
- for (char &K : Str)
- K = (K == '|' ? '+' : '-');
-}
-
-void DiagText::print(std::vector<std::string> &RST) {
- if (Pieces.empty()) {
- RST.push_back("");
- return;
- }
-
- if (Pieces.size() == 1)
- return Pieces[0]->print(RST);
-
- std::string EmptyLinePrefix;
- size_t Start = RST.size();
- bool HasMultipleLines = true;
- for (auto &P : Pieces) {
- std::vector<std::string> Lines;
- P->print(Lines);
- if (Lines.empty())
- continue;
-
- // We need a vertical separator if either this or the previous piece is a
- // multi-line piece, or this is the last piece.
- const char *Separator = (Lines.size() > 1 || HasMultipleLines) ? "|" : "";
- HasMultipleLines = Lines.size() > 1;
-
- if (Start + Lines.size() > RST.size())
- RST.resize(Start + Lines.size(), EmptyLinePrefix);
-
- padToSameLength(Lines.begin(), Lines.end());
- for (size_t I = 0; I != Lines.size(); ++I)
- RST[Start + I] += Separator + Lines[I];
- std::string Empty(Lines[0].size(), ' ');
- for (size_t I = Start + Lines.size(); I != RST.size(); ++I)
- RST[I] += Separator + Empty;
- EmptyLinePrefix += Separator + Empty;
- }
- for (size_t I = Start; I != RST.size(); ++I)
- RST[I] += "|";
- EmptyLinePrefix += "|";
-
- makeRowSeparator(EmptyLinePrefix);
- RST.insert(RST.begin() + Start, EmptyLinePrefix);
- RST.insert(RST.end(), EmptyLinePrefix);
-}
-
-void DiagText::TextPiece::print(std::vector<std::string> &RST) {
- RST.push_back("");
- auto &S = RST.back();
-
- StringRef T = Text;
- while (!T.empty() && T.front() == ' ') {
- RST.back() += " |nbsp| ";
- T = T.drop_front();
- }
-
- std::string Suffix;
- while (!T.empty() && T.back() == ' ') {
- Suffix += " |nbsp| ";
- T = T.drop_back();
- }
-
- if (!T.empty()) {
- S += ':';
- S += Role;
- S += ":`";
- escapeRST(T, S);
- S += '`';
- }
-
- S += Suffix;
-}
-
-void DiagText::PlaceholderPiece::print(std::vector<std::string> &RST) {
- RST.push_back(std::string(":placeholder:`") + char('A' + Index) + "`");
-}
-
-void DiagText::SelectPiece::print(std::vector<std::string> &RST) {
- std::vector<size_t> SeparatorIndexes;
- SeparatorIndexes.push_back(RST.size());
- RST.emplace_back();
- for (auto &O : Options) {
- O.print(RST);
- SeparatorIndexes.push_back(RST.size());
- RST.emplace_back();
- }
-
- makeTableRows(RST.begin() + SeparatorIndexes.front(),
- RST.begin() + SeparatorIndexes.back() + 1);
- for (size_t I : SeparatorIndexes)
- makeRowSeparator(RST[I]);
-}
-
bool isRemarkGroup(const Record *DiagGroup,
const std::map<std::string, GroupInfo> &DiagsInGroup) {
bool AnyRemarks = false, AnyNonRemarks = false;
@@ -1206,12 +1658,13 @@ void writeHeader(StringRef Str, raw_ostream &OS, char Kind = '-') {
OS << Str << "\n" << std::string(Str.size(), Kind) << "\n";
}
-void writeDiagnosticText(StringRef Role, StringRef Text, raw_ostream &OS) {
+void writeDiagnosticText(DiagnosticTextBuilder &Builder, const Record *R,
+ StringRef Role, raw_ostream &OS) {
+ StringRef Text = R->getValueAsString("Text");
if (Text == "%0")
OS << "The text of this diagnostic is not controlled by Clang.\n\n";
else {
- std::vector<std::string> Out;
- DiagText(Role, Text).print(Out);
+ std::vector<std::string> Out = Builder.buildForDocumentation(Role, R);
for (auto &Line : Out)
OS << Line << "\n";
OS << "\n";
@@ -1234,11 +1687,14 @@ void EmitClangDiagDocs(RecordKeeper &Records, raw_ostream &OS) {
OS << Documentation->getValueAsString("Intro") << "\n";
+ DiagnosticTextBuilder Builder(Records);
+
std::vector<Record*> Diags =
Records.getAllDerivedDefinitions("Diagnostic");
+
std::vector<Record*> DiagGroups =
Records.getAllDerivedDefinitions("DiagGroup");
- std::sort(DiagGroups.begin(), DiagGroups.end(), diagGroupBeforeByName);
+ llvm::sort(DiagGroups.begin(), DiagGroups.end(), diagGroupBeforeByName);
DiagGroupParentMap DGParentMap(Records);
@@ -1257,10 +1713,10 @@ void EmitClangDiagDocs(RecordKeeper &Records, raw_ostream &OS) {
DiagsInPedanticSet.end());
RecordVec GroupsInPedantic(GroupsInPedanticSet.begin(),
GroupsInPedanticSet.end());
- std::sort(DiagsInPedantic.begin(), DiagsInPedantic.end(),
- beforeThanCompare);
- std::sort(GroupsInPedantic.begin(), GroupsInPedantic.end(),
- beforeThanCompare);
+ llvm::sort(DiagsInPedantic.begin(), DiagsInPedantic.end(),
+ beforeThanCompare);
+ llvm::sort(GroupsInPedantic.begin(), GroupsInPedantic.end(),
+ beforeThanCompare);
PedDiags.DiagsInGroup.insert(PedDiags.DiagsInGroup.end(),
DiagsInPedantic.begin(),
DiagsInPedantic.end());
@@ -1309,7 +1765,7 @@ void EmitClangDiagDocs(RecordKeeper &Records, raw_ostream &OS) {
OS << "Also controls ";
bool First = true;
- std::sort(GroupInfo.SubGroups.begin(), GroupInfo.SubGroups.end());
+ llvm::sort(GroupInfo.SubGroups.begin(), GroupInfo.SubGroups.end());
for (const auto &Name : GroupInfo.SubGroups) {
if (!First) OS << ", ";
OS << "`" << (IsRemarkGroup ? "-R" : "-W") << Name << "`_";
@@ -1325,7 +1781,8 @@ void EmitClangDiagDocs(RecordKeeper &Records, raw_ostream &OS) {
Severity[0] = tolower(Severity[0]);
if (Severity == "ignored")
Severity = IsRemarkGroup ? "remark" : "warning";
- writeDiagnosticText(Severity, D->getValueAsString("Text"), OS);
+
+ writeDiagnosticText(Builder, D, Severity, OS);
}
}
diff --git a/utils/TableGen/ClangOptionDocEmitter.cpp b/utils/TableGen/ClangOptionDocEmitter.cpp
index 59314510e0ad..7fe487e54698 100644
--- a/utils/TableGen/ClangOptionDocEmitter.cpp
+++ b/utils/TableGen/ClangOptionDocEmitter.cpp
@@ -111,7 +111,7 @@ Documentation extractDocumentation(RecordKeeper &Records) {
auto DocumentationForOption = [&](Record *R) -> DocumentedOption {
auto &A = Aliases[R];
- std::sort(A.begin(), A.end(), CompareByName);
+ llvm::sort(A.begin(), A.end(), CompareByName);
return {R, std::move(A)};
};
@@ -120,7 +120,7 @@ Documentation extractDocumentation(RecordKeeper &Records) {
Documentation D;
auto &Groups = GroupsInGroup[R];
- std::sort(Groups.begin(), Groups.end(), CompareByLocation);
+ llvm::sort(Groups.begin(), Groups.end(), CompareByLocation);
for (Record *G : Groups) {
D.Groups.emplace_back();
D.Groups.back().Group = G;
@@ -129,7 +129,7 @@ Documentation extractDocumentation(RecordKeeper &Records) {
}
auto &Options = OptionsInGroup[R];
- std::sort(Options.begin(), Options.end(), CompareByName);
+ llvm::sort(Options.begin(), Options.end(), CompareByName);
for (Record *O : Options)
D.Options.push_back(DocumentationForOption(O));
@@ -245,19 +245,27 @@ void emitOptionWithArgs(StringRef Prefix, const Record *Option,
void emitOptionName(StringRef Prefix, const Record *Option, raw_ostream &OS) {
// Find the arguments to list after the option.
unsigned NumArgs = getNumArgsForKind(Option->getValueAsDef("Kind"), Option);
+ bool HasMetaVarName = !Option->isValueUnset("MetaVarName");
std::vector<std::string> Args;
- if (!Option->isValueUnset("MetaVarName"))
+ if (HasMetaVarName)
Args.push_back(Option->getValueAsString("MetaVarName"));
else if (NumArgs == 1)
Args.push_back("<arg>");
- while (Args.size() < NumArgs) {
- Args.push_back(("<arg" + Twine(Args.size() + 1) + ">").str());
- // Use '--args <arg1> <arg2>...' if any number of args are allowed.
- if (Args.size() == 2 && NumArgs == UnlimitedArgs) {
- Args.back() += "...";
- break;
+ // Fill up arguments if this option didn't provide a meta var name or it
+ // supports an unlimited number of arguments. We can't see how many arguments
+ // already are in a meta var name, so assume it has right number. This is
+ // needed for JoinedAndSeparate options so that there arent't too many
+ // arguments.
+ if (!HasMetaVarName || NumArgs == UnlimitedArgs) {
+ while (Args.size() < NumArgs) {
+ Args.push_back(("<arg" + Twine(Args.size() + 1) + ">").str());
+ // Use '--args <arg1> <arg2>...' if any number of args are allowed.
+ if (Args.size() == 2 && NumArgs == UnlimitedArgs) {
+ Args.back() += "...";
+ break;
+ }
}
}
diff --git a/utils/TableGen/ClangSACheckersEmitter.cpp b/utils/TableGen/ClangSACheckersEmitter.cpp
index 8f3de0b67d77..4fda8e47e4a8 100644
--- a/utils/TableGen/ClangSACheckersEmitter.cpp
+++ b/utils/TableGen/ClangSACheckersEmitter.cpp
@@ -23,7 +23,7 @@ using namespace llvm;
// Static Analyzer Checkers Tables generation
//===----------------------------------------------------------------------===//
-/// \brief True if it is specified hidden or a parent package is specified
+/// True if it is specified hidden or a parent package is specified
/// as hidden, otherwise false.
static bool isHidden(const Record &R) {
if (R.getValueAsBit("Hidden"))
diff --git a/utils/TableGen/NeonEmitter.cpp b/utils/TableGen/NeonEmitter.cpp
index 8117d2f4a232..eca03a5892e2 100644
--- a/utils/TableGen/NeonEmitter.cpp
+++ b/utils/TableGen/NeonEmitter.cpp
@@ -304,7 +304,7 @@ class Intrinsic {
ListInit *Body;
/// The architectural #ifdef guard.
std::string Guard;
- /// Set if the Unvailable bit is 1. This means we don't generate a body,
+ /// Set if the Unavailable bit is 1. This means we don't generate a body,
/// just an "unavailable" attribute on a declaration.
bool IsUnavailable;
/// Is this intrinsic safe for big-endian? or does it need its arguments
@@ -552,7 +552,11 @@ public:
// run - Emit arm_neon.h.inc
void run(raw_ostream &o);
+ // runFP16 - Emit arm_fp16.h.inc
+ void runFP16(raw_ostream &o);
+
// runHeader - Emit all the __builtin prototypes used in arm_neon.h
+ // and arm_fp16.h
void runHeader(raw_ostream &o);
// runTests - Emit tests for all the Neon intrinsics.
@@ -852,6 +856,35 @@ void Type::applyModifier(char Mod) {
NumVectors = 0;
Float = true;
break;
+ case 'Y':
+ Bitwidth = ElementBitwidth = 16;
+ NumVectors = 0;
+ Float = true;
+ break;
+ case 'I':
+ Bitwidth = ElementBitwidth = 32;
+ NumVectors = 0;
+ Float = false;
+ Signed = true;
+ break;
+ case 'L':
+ Bitwidth = ElementBitwidth = 64;
+ NumVectors = 0;
+ Float = false;
+ Signed = true;
+ break;
+ case 'U':
+ Bitwidth = ElementBitwidth = 32;
+ NumVectors = 0;
+ Float = false;
+ Signed = false;
+ break;
+ case 'O':
+ Bitwidth = ElementBitwidth = 64;
+ NumVectors = 0;
+ Float = false;
+ Signed = false;
+ break;
case 'f':
Float = true;
ElementBitwidth = 32;
@@ -915,7 +948,7 @@ void Type::applyModifier(char Mod) {
break;
case 'c':
Constant = true;
- // Fall through
+ LLVM_FALLTHROUGH;
case 'p':
Pointer = true;
Bitwidth = ElementBitwidth;
@@ -962,6 +995,19 @@ void Type::applyModifier(char Mod) {
if (!AppliedQuad)
Bitwidth *= 2;
break;
+ case '7':
+ if (AppliedQuad)
+ Bitwidth /= 2;
+ ElementBitwidth = 8;
+ break;
+ case '8':
+ ElementBitwidth = 8;
+ break;
+ case '9':
+ if (!AppliedQuad)
+ Bitwidth *= 2;
+ ElementBitwidth = 8;
+ break;
default:
llvm_unreachable("Unhandled character!");
}
@@ -1010,7 +1056,7 @@ std::string Intrinsic::getInstTypeCode(Type T, ClassKind CK) const {
}
static bool isFloatingPointProtoModifier(char Mod) {
- return Mod == 'F' || Mod == 'f' || Mod == 'H';
+ return Mod == 'F' || Mod == 'f' || Mod == 'H' || Mod == 'Y' || Mod == 'I';
}
std::string Intrinsic::getBuiltinTypeStr() {
@@ -1974,7 +2020,7 @@ void NeonEmitter::createIntrinsic(Record *R,
}
}
- std::sort(NewTypeSpecs.begin(), NewTypeSpecs.end());
+ llvm::sort(NewTypeSpecs.begin(), NewTypeSpecs.end());
NewTypeSpecs.erase(std::unique(NewTypeSpecs.begin(), NewTypeSpecs.end()),
NewTypeSpecs.end());
auto &Entry = IntrinsicMap[Name];
@@ -2116,8 +2162,7 @@ void NeonEmitter::genOverloadTypeCheckCode(raw_ostream &OS,
OS << "#endif\n\n";
}
-void
-NeonEmitter::genIntrinsicRangeCheckCode(raw_ostream &OS,
+void NeonEmitter::genIntrinsicRangeCheckCode(raw_ostream &OS,
SmallVectorImpl<Intrinsic *> &Defs) {
OS << "#ifdef GET_NEON_IMMEDIATE_CHECK\n";
@@ -2142,11 +2187,15 @@ NeonEmitter::genIntrinsicRangeCheckCode(raw_ostream &OS,
Record *R = Def->getRecord();
if (R->getValueAsBit("isVCVT_N")) {
// VCVT between floating- and fixed-point values takes an immediate
- // in the range [1, 32) for f32 or [1, 64) for f64.
+ // in the range [1, 32) for f32 or [1, 64) for f64 or [1, 16) for f16.
LowerBound = "1";
- if (Def->getBaseType().getElementSizeInBits() == 32)
+ if (Def->getBaseType().getElementSizeInBits() == 16 ||
+ Def->getName().find('h') != std::string::npos)
+ // VCVTh operating on FP16 intrinsics in range [1, 16)
+ UpperBound = "15";
+ else if (Def->getBaseType().getElementSizeInBits() == 32)
UpperBound = "31";
- else
+ else
UpperBound = "63";
} else if (R->getValueAsBit("isScalarShift")) {
// Right shifts have an 'r' in the name, left shifts do not. Convert
@@ -2420,12 +2469,125 @@ void NeonEmitter::run(raw_ostream &OS) {
OS << "#endif /* __ARM_NEON_H */\n";
}
+/// run - Read the records in arm_fp16.td and output arm_fp16.h. arm_fp16.h
+/// is comprised of type definitions and function declarations.
+void NeonEmitter::runFP16(raw_ostream &OS) {
+ OS << "/*===---- arm_fp16.h - ARM FP16 intrinsics "
+ "------------------------------"
+ "---===\n"
+ " *\n"
+ " * Permission is hereby granted, free of charge, to any person "
+ "obtaining a copy\n"
+ " * of this software and associated documentation files (the "
+ "\"Software\"), to deal\n"
+ " * in the Software without restriction, including without limitation "
+ "the rights\n"
+ " * to use, copy, modify, merge, publish, distribute, sublicense, "
+ "and/or sell\n"
+ " * copies of the Software, and to permit persons to whom the Software "
+ "is\n"
+ " * furnished to do so, subject to the following conditions:\n"
+ " *\n"
+ " * The above copyright notice and this permission notice shall be "
+ "included in\n"
+ " * all copies or substantial portions of the Software.\n"
+ " *\n"
+ " * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, "
+ "EXPRESS OR\n"
+ " * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF "
+ "MERCHANTABILITY,\n"
+ " * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT "
+ "SHALL THE\n"
+ " * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR "
+ "OTHER\n"
+ " * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, "
+ "ARISING FROM,\n"
+ " * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER "
+ "DEALINGS IN\n"
+ " * THE SOFTWARE.\n"
+ " *\n"
+ " *===-----------------------------------------------------------------"
+ "---"
+ "---===\n"
+ " */\n\n";
+
+ OS << "#ifndef __ARM_FP16_H\n";
+ OS << "#define __ARM_FP16_H\n\n";
+
+ OS << "#include <stdint.h>\n\n";
+
+ OS << "typedef __fp16 float16_t;\n";
+
+ OS << "#define __ai static inline __attribute__((__always_inline__, "
+ "__nodebug__))\n\n";
+
+ SmallVector<Intrinsic *, 128> Defs;
+ std::vector<Record *> RV = Records.getAllDerivedDefinitions("Inst");
+ for (auto *R : RV)
+ createIntrinsic(R, Defs);
+
+ for (auto *I : Defs)
+ I->indexBody();
+
+ std::stable_sort(
+ Defs.begin(), Defs.end(),
+ [](const Intrinsic *A, const Intrinsic *B) { return *A < *B; });
+
+ // Only emit a def when its requirements have been met.
+ // FIXME: This loop could be made faster, but it's fast enough for now.
+ bool MadeProgress = true;
+ std::string InGuard;
+ while (!Defs.empty() && MadeProgress) {
+ MadeProgress = false;
+
+ for (SmallVector<Intrinsic *, 128>::iterator I = Defs.begin();
+ I != Defs.end(); /*No step*/) {
+ bool DependenciesSatisfied = true;
+ for (auto *II : (*I)->getDependencies()) {
+ if (std::find(Defs.begin(), Defs.end(), II) != Defs.end())
+ DependenciesSatisfied = false;
+ }
+ if (!DependenciesSatisfied) {
+ // Try the next one.
+ ++I;
+ continue;
+ }
+
+ // Emit #endif/#if pair if needed.
+ if ((*I)->getGuard() != InGuard) {
+ if (!InGuard.empty())
+ OS << "#endif\n";
+ InGuard = (*I)->getGuard();
+ if (!InGuard.empty())
+ OS << "#if " << InGuard << "\n";
+ }
+
+ // Actually generate the intrinsic code.
+ OS << (*I)->generate();
+
+ MadeProgress = true;
+ I = Defs.erase(I);
+ }
+ }
+ assert(Defs.empty() && "Some requirements were not satisfied!");
+ if (!InGuard.empty())
+ OS << "#endif\n";
+
+ OS << "\n";
+ OS << "#undef __ai\n\n";
+ OS << "#endif /* __ARM_FP16_H */\n";
+}
+
namespace clang {
void EmitNeon(RecordKeeper &Records, raw_ostream &OS) {
NeonEmitter(Records).run(OS);
}
+void EmitFP16(RecordKeeper &Records, raw_ostream &OS) {
+ NeonEmitter(Records).runFP16(OS);
+}
+
void EmitNeonSema(RecordKeeper &Records, raw_ostream &OS) {
NeonEmitter(Records).runHeader(OS);
}
diff --git a/utils/TableGen/TableGen.cpp b/utils/TableGen/TableGen.cpp
index 840b330a732c..a2ba131628f1 100644
--- a/utils/TableGen/TableGen.cpp
+++ b/utils/TableGen/TableGen.cpp
@@ -52,6 +52,7 @@ enum ActionType {
GenClangCommentCommandInfo,
GenClangCommentCommandList,
GenArmNeon,
+ GenArmFP16,
GenArmNeonSema,
GenArmNeonTest,
GenAttrDocs,
@@ -139,6 +140,7 @@ cl::opt<ActionType> Action(
"Generate list of commands that are used in "
"documentation comments"),
clEnumValN(GenArmNeon, "gen-arm-neon", "Generate arm_neon.h for clang"),
+ clEnumValN(GenArmFP16, "gen-arm-fp16", "Generate arm_fp16.h for clang"),
clEnumValN(GenArmNeonSema, "gen-arm-neon-sema",
"Generate ARM NEON sema support for clang"),
clEnumValN(GenArmNeonTest, "gen-arm-neon-test",
@@ -250,6 +252,9 @@ bool ClangTableGenMain(raw_ostream &OS, RecordKeeper &Records) {
case GenArmNeon:
EmitNeon(Records, OS);
break;
+ case GenArmFP16:
+ EmitFP16(Records, OS);
+ break;
case GenArmNeonSema:
EmitNeonSema(Records, OS);
break;
diff --git a/utils/TableGen/TableGenBackends.h b/utils/TableGen/TableGenBackends.h
index 342c889ca47a..706e812ae874 100644
--- a/utils/TableGen/TableGenBackends.h
+++ b/utils/TableGen/TableGenBackends.h
@@ -65,6 +65,7 @@ void EmitClangCommentCommandInfo(RecordKeeper &Records, raw_ostream &OS);
void EmitClangCommentCommandList(RecordKeeper &Records, raw_ostream &OS);
void EmitNeon(RecordKeeper &Records, raw_ostream &OS);
+void EmitFP16(RecordKeeper &Records, raw_ostream &OS);
void EmitNeonSema(RecordKeeper &Records, raw_ostream &OS);
void EmitNeonTest(RecordKeeper &Records, raw_ostream &OS);
void EmitNeon2(RecordKeeper &Records, raw_ostream &OS);
diff --git a/utils/analyzer/CmpRuns.py b/utils/analyzer/CmpRuns.py
index 2c0ed6aae3a2..1b8fe7bd698d 100755
--- a/utils/analyzer/CmpRuns.py
+++ b/utils/analyzer/CmpRuns.py
@@ -26,9 +26,25 @@ Usage:
"""
+from collections import defaultdict
+
+from math import log
+from optparse import OptionParser
+import json
import os
import plistlib
+import re
+import sys
+
+STATS_REGEXP = re.compile(r"Statistics: (\{.+\})", re.MULTILINE | re.DOTALL)
+class Colors:
+ """
+ Color for terminal highlight.
+ """
+ RED = '\x1b[2;30;41m'
+ GREEN = '\x1b[6;30;42m'
+ CLEAR = '\x1b[0m'
# Information about analysis run:
# path - the analysis output directory
@@ -47,6 +63,7 @@ class AnalysisDiagnostic:
self._loc = self._data['location']
self._report = report
self._htmlReport = htmlReport
+ self._reportSize = len(self._data['path'])
def getFileName(self):
root = self._report.run.root
@@ -61,6 +78,9 @@ class AnalysisDiagnostic:
def getColumn(self):
return self._loc['col']
+ def getPathLength(self):
+ return self._reportSize
+
def getCategory(self):
return self._data['category']
@@ -81,9 +101,15 @@ class AnalysisDiagnostic:
return os.path.join(self._report.run.path, self._htmlReport)
def getReadableName(self):
- return '%s:%d:%d, %s: %s' % (self.getFileName(), self.getLine(),
- self.getColumn(), self.getCategory(),
- self.getDescription())
+ if 'issue_context' in self._data:
+ funcnamePostfix = "#" + self._data['issue_context']
+ else:
+ funcnamePostfix = ""
+ return '%s%s:%d:%d, %s: %s' % (self.getFileName(),
+ funcnamePostfix,
+ self.getLine(),
+ self.getColumn(), self.getCategory(),
+ self.getDescription())
# Note, the data format is not an API and may change from one analyzer
# version to another.
@@ -91,13 +117,6 @@ class AnalysisDiagnostic:
return self._data
-class CmpOptions:
- def __init__(self, verboseLog=None, rootA="", rootB=""):
- self.rootA = rootA
- self.rootB = rootB
- self.verboseLog = verboseLog
-
-
class AnalysisReport:
def __init__(self, run, files):
self.run = run
@@ -114,12 +133,16 @@ class AnalysisRun:
# Cumulative list of all diagnostics from all the reports.
self.diagnostics = []
self.clang_version = None
+ self.stats = []
def getClangVersion(self):
return self.clang_version
def readSingleFile(self, p, deleteEmpty):
data = plistlib.readPlist(p)
+ if 'statistics' in data:
+ self.stats.append(json.loads(data['statistics']))
+ data.pop('statistics')
# We want to retrieve the clang version even if there are no
# reports. Assume that all reports were created using the same
@@ -193,19 +216,20 @@ def cmpAnalysisDiagnostic(d):
return d.getIssueIdentifier()
-def compareResults(A, B):
+def compareResults(A, B, opts):
"""
compareResults - Generate a relation from diagnostics in run A to
diagnostics in run B.
- The result is the relation as a list of triples (a, b, confidence) where
- each element {a,b} is None or an element from the respective run, and
- confidence is a measure of the match quality (where 0 indicates equality,
- and None is used if either element is None).
+ The result is the relation as a list of triples (a, b) where
+ each element {a,b} is None or a matching element from the respective run
"""
res = []
+ # Map size_before -> size_after
+ path_difference_data = []
+
# Quickly eliminate equal elements.
neqA = []
neqB = []
@@ -217,7 +241,18 @@ def compareResults(A, B):
a = eltsA.pop()
b = eltsB.pop()
if (a.getIssueIdentifier() == b.getIssueIdentifier()):
- res.append((a, b, 0))
+ if a.getPathLength() != b.getPathLength():
+ if opts.relative_path_histogram:
+ path_difference_data.append(
+ float(a.getPathLength()) / b.getPathLength())
+ elif opts.relative_log_path_histogram:
+ path_difference_data.append(
+ log(float(a.getPathLength()) / b.getPathLength()))
+ elif opts.absolute_path_histogram:
+ path_difference_data.append(
+ a.getPathLength() - b.getPathLength())
+
+ res.append((a, b))
elif a.getIssueIdentifier() > b.getIssueIdentifier():
eltsB.append(b)
neqA.append(a)
@@ -234,17 +269,65 @@ def compareResults(A, B):
# in any way on the diagnostic format.
for a in neqA:
- res.append((a, None, None))
+ res.append((a, None))
for b in neqB:
- res.append((None, b, None))
+ res.append((None, b))
- return res
+ if opts.relative_log_path_histogram or opts.relative_path_histogram or \
+ opts.absolute_path_histogram:
+ from matplotlib import pyplot
+ pyplot.hist(path_difference_data, bins=100)
+ pyplot.show()
+ return res
-def dumpScanBuildResultsDiff(dirA, dirB, opts, deleteEmpty=True):
+def deriveStats(results):
+ # Assume all keys are the same in each statistics bucket.
+ combined_data = defaultdict(list)
+ for stat in results.stats:
+ for key, value in stat.iteritems():
+ combined_data[key].append(value)
+ combined_stats = {}
+ for key, values in combined_data.iteritems():
+ combined_stats[str(key)] = {
+ "max": max(values),
+ "min": min(values),
+ "mean": sum(values) / len(values),
+ "median": sorted(values)[len(values) / 2],
+ "total": sum(values)
+ }
+ return combined_stats
+
+
+def compareStats(resultsA, resultsB):
+ statsA = deriveStats(resultsA)
+ statsB = deriveStats(resultsB)
+ keys = sorted(statsA.keys())
+ for key in keys:
+ print key
+ for kkey in statsA[key]:
+ valA = float(statsA[key][kkey])
+ valB = float(statsB[key][kkey])
+ report = "%.3f -> %.3f" % (valA, valB)
+ # Only apply highlighting when writing to TTY and it's not Windows
+ if sys.stdout.isatty() and os.name != 'nt':
+ if valB != 0:
+ ratio = (valB - valA) / valB
+ if ratio < -0.2:
+ report = Colors.GREEN + report + Colors.CLEAR
+ elif ratio > 0.2:
+ report = Colors.RED + report + Colors.CLEAR
+ print "\t %s %s" % (kkey, report)
+
+def dumpScanBuildResultsDiff(dirA, dirB, opts, deleteEmpty=True,
+ Stdout=sys.stdout):
# Load the run results.
resultsA = loadResults(dirA, opts, opts.rootA, deleteEmpty)
resultsB = loadResults(dirB, opts, opts.rootB, deleteEmpty)
+ if resultsA.stats:
+ compareStats(resultsA, resultsB)
+ if opts.stats_only:
+ return
# Open the verbose log, if given.
if opts.verboseLog:
@@ -252,47 +335,41 @@ def dumpScanBuildResultsDiff(dirA, dirB, opts, deleteEmpty=True):
else:
auxLog = None
- diff = compareResults(resultsA, resultsB)
+ diff = compareResults(resultsA, resultsB, opts)
foundDiffs = 0
+ totalAdded = 0
+ totalRemoved = 0
for res in diff:
- a, b, confidence = res
+ a, b = res
if a is None:
- print "ADDED: %r" % b.getReadableName()
+ Stdout.write("ADDED: %r\n" % b.getReadableName())
foundDiffs += 1
+ totalAdded += 1
if auxLog:
- print >>auxLog, ("('ADDED', %r, %r)" % (b.getReadableName(),
- b.getReport()))
+ auxLog.write("('ADDED', %r, %r)\n" % (b.getReadableName(),
+ b.getReport()))
elif b is None:
- print "REMOVED: %r" % a.getReadableName()
- foundDiffs += 1
- if auxLog:
- print >>auxLog, ("('REMOVED', %r, %r)" % (a.getReadableName(),
- a.getReport()))
- elif confidence:
- print "CHANGED: %r to %r" % (a.getReadableName(),
- b.getReadableName())
+ Stdout.write("REMOVED: %r\n" % a.getReadableName())
foundDiffs += 1
+ totalRemoved += 1
if auxLog:
- print >>auxLog, ("('CHANGED', %r, %r, %r, %r)"
- % (a.getReadableName(),
- b.getReadableName(),
- a.getReport(),
- b.getReport()))
+ auxLog.write("('REMOVED', %r, %r)\n" % (a.getReadableName(),
+ a.getReport()))
else:
pass
TotalReports = len(resultsB.diagnostics)
- print "TOTAL REPORTS: %r" % TotalReports
- print "TOTAL DIFFERENCES: %r" % foundDiffs
+ Stdout.write("TOTAL REPORTS: %r\n" % TotalReports)
+ Stdout.write("TOTAL ADDED: %r\n" % totalAdded)
+ Stdout.write("TOTAL REMOVED: %r\n" % totalRemoved)
if auxLog:
- print >>auxLog, "('TOTAL NEW REPORTS', %r)" % TotalReports
- print >>auxLog, "('TOTAL DIFFERENCES', %r)" % foundDiffs
+ auxLog.write("('TOTAL NEW REPORTS', %r)\n" % TotalReports)
+ auxLog.write("('TOTAL DIFFERENCES', %r)\n" % foundDiffs)
+ auxLog.close()
return foundDiffs, len(resultsA.diagnostics), len(resultsB.diagnostics)
-
-def main():
- from optparse import OptionParser
+def generate_option_parser():
parser = OptionParser("usage: %prog [options] [dir A] [dir B]")
parser.add_option("", "--rootA", dest="rootA",
help="Prefix to ignore on source files for directory A",
@@ -302,9 +379,31 @@ def main():
action="store", type=str, default="")
parser.add_option("", "--verbose-log", dest="verboseLog",
help="Write additional information to LOG \
- [default=None]",
+ [default=None]",
action="store", type=str, default=None,
metavar="LOG")
+ parser.add_option("--relative-path-differences-histogram",
+ action="store_true", dest="relative_path_histogram",
+ default=False,
+ help="Show histogram of relative paths differences. \
+ Requires matplotlib")
+ parser.add_option("--relative-log-path-differences-histogram",
+ action="store_true", dest="relative_log_path_histogram",
+ default=False,
+ help="Show histogram of log relative paths differences. \
+ Requires matplotlib")
+ parser.add_option("--absolute-path-differences-histogram",
+ action="store_true", dest="absolute_path_histogram",
+ default=False,
+ help="Show histogram of absolute paths differences. \
+ Requires matplotlib")
+ parser.add_option("--stats-only", action="store_true", dest="stats_only",
+ default=False, help="Only show statistics on reports")
+ return parser
+
+
+def main():
+ parser = generate_option_parser()
(opts, args) = parser.parse_args()
if len(args) != 2:
diff --git a/utils/analyzer/SATestAdd.py b/utils/analyzer/SATestAdd.py
index 4c3e35cdcb5d..041b24409fe3 100644
--- a/utils/analyzer/SATestAdd.py
+++ b/utils/analyzer/SATestAdd.py
@@ -32,11 +32,11 @@ the Repository Directory.
(e.g., to adapt to newer version of clang)
that should be applied to CachedSource
before analysis. To construct this patch,
- run the the download script to download
+ run the download script to download
the project to CachedSource, copy the
CachedSource to another directory (for
example, PatchedSource) and make any
- needed modifications to the the copied
+ needed modifications to the copied
source.
Then run:
diff -ur CachedSource PatchedSource \
diff --git a/utils/analyzer/SATestBuild.py b/utils/analyzer/SATestBuild.py
index 60c8796e338f..4da025aa53b3 100644
--- a/utils/analyzer/SATestBuild.py
+++ b/utils/analyzer/SATestBuild.py
@@ -45,32 +45,55 @@ variable. It should contain a comma separated list.
import CmpRuns
import SATestUtils
-import os
+from subprocess import CalledProcessError, check_call
+import argparse
import csv
-import sys
import glob
+import logging
import math
+import multiprocessing
+import os
+import plistlib
import shutil
+import sys
+import threading
import time
-import plistlib
-import argparse
-from subprocess import check_call, CalledProcessError
-import multiprocessing
+import Queue
#------------------------------------------------------------------------------
# Helper functions.
#------------------------------------------------------------------------------
+Local = threading.local()
+Local.stdout = sys.stdout
+Local.stderr = sys.stderr
+logging.basicConfig(
+ level=logging.DEBUG,
+ format='%(asctime)s:%(levelname)s:%(name)s: %(message)s')
-sys.stdout = SATestUtils.flushfile(sys.stdout)
+class StreamToLogger(object):
+ def __init__(self, logger, log_level=logging.INFO):
+ self.logger = logger
+ self.log_level = log_level
+
+ def write(self, buf):
+ # Rstrip in order not to write an extra newline.
+ self.logger.log(self.log_level, buf.rstrip())
+
+ def flush(self):
+ pass
+
+ def fileno(self):
+ return 0
def getProjectMapPath():
ProjectMapPath = os.path.join(os.path.abspath(os.curdir),
ProjectMapFile)
if not os.path.exists(ProjectMapPath):
- print "Error: Cannot find the Project Map file " + ProjectMapPath +\
- "\nRunning script for the wrong directory?"
+ Local.stdout.write("Error: Cannot find the Project Map file "
+ + ProjectMapPath
+ + "\nRunning script for the wrong directory?\n")
sys.exit(1)
return ProjectMapPath
@@ -100,7 +123,7 @@ if not Clang:
sys.exit(1)
# Number of jobs.
-Jobs = int(math.ceil(multiprocessing.cpu_count() * 0.75))
+MaxJobs = int(math.ceil(multiprocessing.cpu_count() * 0.75))
# Project map stores info about all the "registered" projects.
ProjectMapFile = "projectMap.csv"
@@ -113,6 +136,9 @@ CleanupScript = "cleanup_run_static_analyzer.sh"
# This is a file containing commands for scan-build.
BuildScript = "run_static_analyzer.cmd"
+# A comment in a build script which disables wrapping.
+NoPrefixCmd = "#NOPREFIX"
+
# The log file name.
LogFolderName = "Logs"
BuildLogName = "run_static_analyzer.log"
@@ -157,7 +183,7 @@ Checkers = ",".join([
"nullability"
])
-Verbose = 1
+Verbose = 0
#------------------------------------------------------------------------------
# Test harness logic.
@@ -170,7 +196,8 @@ def runCleanupScript(Dir, PBuildLogFile):
"""
Cwd = os.path.join(Dir, PatchedSourceDirName)
ScriptPath = os.path.join(Dir, CleanupScript)
- SATestUtils.runScript(ScriptPath, PBuildLogFile, Cwd)
+ SATestUtils.runScript(ScriptPath, PBuildLogFile, Cwd,
+ Stdout=Local.stdout, Stderr=Local.stderr)
def runDownloadScript(Dir, PBuildLogFile):
@@ -178,7 +205,8 @@ def runDownloadScript(Dir, PBuildLogFile):
Run the script to download the project, if it exists.
"""
ScriptPath = os.path.join(Dir, DownloadScript)
- SATestUtils.runScript(ScriptPath, PBuildLogFile, Dir)
+ SATestUtils.runScript(ScriptPath, PBuildLogFile, Dir,
+ Stdout=Local.stdout, Stderr=Local.stderr)
def downloadAndPatch(Dir, PBuildLogFile):
@@ -192,8 +220,8 @@ def downloadAndPatch(Dir, PBuildLogFile):
if not os.path.exists(CachedSourceDirPath):
runDownloadScript(Dir, PBuildLogFile)
if not os.path.exists(CachedSourceDirPath):
- print "Error: '%s' not found after download." % (
- CachedSourceDirPath)
+ Local.stderr.write("Error: '%s' not found after download.\n" % (
+ CachedSourceDirPath))
exit(1)
PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
@@ -211,10 +239,10 @@ def applyPatch(Dir, PBuildLogFile):
PatchfilePath = os.path.join(Dir, PatchfileName)
PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
if not os.path.exists(PatchfilePath):
- print " No local patches."
+ Local.stdout.write(" No local patches.\n")
return
- print " Applying patch."
+ Local.stdout.write(" Applying patch.\n")
try:
check_call("patch -p1 < '%s'" % (PatchfilePath),
cwd=PatchedSourceDirPath,
@@ -222,7 +250,8 @@ def applyPatch(Dir, PBuildLogFile):
stdout=PBuildLogFile,
shell=True)
except:
- print "Error: Patch failed. See %s for details." % (PBuildLogFile.name)
+ Local.stderr.write("Error: Patch failed. See %s for details.\n" % (
+ PBuildLogFile.name))
sys.exit(1)
@@ -233,7 +262,8 @@ def runScanBuild(Dir, SBOutputDir, PBuildLogFile):
"""
BuildScriptPath = os.path.join(Dir, BuildScript)
if not os.path.exists(BuildScriptPath):
- print "Error: build script is not defined: %s" % BuildScriptPath
+ Local.stderr.write(
+ "Error: build script is not defined: %s\n" % BuildScriptPath)
sys.exit(1)
AllCheckers = Checkers
@@ -247,9 +277,18 @@ def runScanBuild(Dir, SBOutputDir, PBuildLogFile):
SBOptions += "-plist-html -o '%s' " % SBOutputDir
SBOptions += "-enable-checker " + AllCheckers + " "
SBOptions += "--keep-empty "
+ AnalyzerConfig = [
+ ("stable-report-filename", "true"),
+ ("serialize-stats", "true"),
+ ]
+
+ SBOptions += "-analyzer-config '%s' " % (
+ ",".join("%s=%s" % (key, value) for (key, value) in AnalyzerConfig))
+
# Always use ccc-analyze to ensure that we can locate the failures
# directory.
SBOptions += "--override-compiler "
+ ExtraEnv = {}
try:
SBCommandFile = open(BuildScriptPath, "r")
SBPrefix = "scan-build " + SBOptions + " "
@@ -257,23 +296,35 @@ def runScanBuild(Dir, SBOutputDir, PBuildLogFile):
Command = Command.strip()
if len(Command) == 0:
continue
+
+ # Custom analyzer invocation specified by project.
+ # Communicate required information using environment variables
+ # instead.
+ if Command == NoPrefixCmd:
+ SBPrefix = ""
+ ExtraEnv['OUTPUT'] = SBOutputDir
+ ExtraEnv['CC'] = Clang
+ continue
+
# If using 'make', auto imply a -jX argument
# to speed up analysis. xcodebuild will
# automatically use the maximum number of cores.
if (Command.startswith("make ") or Command == "make") and \
"-j" not in Command:
- Command += " -j%d" % Jobs
+ Command += " -j%d" % MaxJobs
SBCommand = SBPrefix + Command
+
if Verbose == 1:
- print " Executing: %s" % (SBCommand,)
+ Local.stdout.write(" Executing: %s\n" % (SBCommand,))
check_call(SBCommand, cwd=SBCwd,
stderr=PBuildLogFile,
stdout=PBuildLogFile,
+ env=dict(os.environ, **ExtraEnv),
shell=True)
except CalledProcessError:
- print "Error: scan-build failed. Its output was: "
+ Local.stderr.write("Error: scan-build failed. Its output was: \n")
PBuildLogFile.seek(0)
- shutil.copyfileobj(PBuildLogFile, sys.stdout)
+ shutil.copyfileobj(PBuildLogFile, Local.stderr)
sys.exit(1)
@@ -282,8 +333,9 @@ def runAnalyzePreprocessed(Dir, SBOutputDir, Mode):
Run analysis on a set of preprocessed files.
"""
if os.path.exists(os.path.join(Dir, BuildScript)):
- print "Error: The preprocessed files project should not contain %s" % \
- BuildScript
+ Local.stderr.write(
+ "Error: The preprocessed files project should not contain %s\n" % (
+ BuildScript))
raise Exception()
CmdPrefix = Clang + " -cc1 "
@@ -313,7 +365,8 @@ def runAnalyzePreprocessed(Dir, SBOutputDir, Mode):
if SATestUtils.hasNoExtension(FileName):
continue
if not SATestUtils.isValidSingleInputFile(FileName):
- print "Error: Invalid single input file %s." % (FullFileName,)
+ Local.stderr.write(
+ "Error: Invalid single input file %s.\n" % (FullFileName,))
raise Exception()
# Build and call the analyzer command.
@@ -322,14 +375,15 @@ def runAnalyzePreprocessed(Dir, SBOutputDir, Mode):
LogFile = open(os.path.join(FailPath, FileName + ".stderr.txt"), "w+b")
try:
if Verbose == 1:
- print " Executing: %s" % (Command,)
+ Local.stdout.write(" Executing: %s\n" % (Command,))
check_call(Command, cwd=Dir, stderr=LogFile,
stdout=LogFile,
shell=True)
except CalledProcessError, e:
- print "Error: Analyzes of %s failed. See %s for details." \
- "Error code %d." % (
- FullFileName, LogFile.name, e.returncode)
+ Local.stderr.write("Error: Analyzes of %s failed. "
+ "See %s for details."
+ "Error code %d.\n" % (
+ FullFileName, LogFile.name, e.returncode))
Failed = True
finally:
LogFile.close()
@@ -349,7 +403,7 @@ def removeLogFile(SBOutputDir):
if (os.path.exists(BuildLogPath)):
RmCommand = "rm '%s'" % BuildLogPath
if Verbose == 1:
- print " Executing: %s" % (RmCommand,)
+ Local.stdout.write(" Executing: %s\n" % (RmCommand,))
check_call(RmCommand, shell=True)
@@ -357,8 +411,8 @@ def buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild):
TBegin = time.time()
BuildLogPath = getBuildLogPath(SBOutputDir)
- print "Log file: %s" % (BuildLogPath,)
- print "Output directory: %s" % (SBOutputDir, )
+ Local.stdout.write("Log file: %s\n" % (BuildLogPath,))
+ Local.stdout.write("Output directory: %s\n" % (SBOutputDir, ))
removeLogFile(SBOutputDir)
@@ -366,8 +420,9 @@ def buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild):
if (os.path.exists(SBOutputDir)):
RmCommand = "rm -r '%s'" % SBOutputDir
if Verbose == 1:
- print " Executing: %s" % (RmCommand,)
- check_call(RmCommand, shell=True)
+ Local.stdout.write(" Executing: %s\n" % (RmCommand,))
+ check_call(RmCommand, shell=True, stdout=Local.stdout,
+ stderr=Local.stderr)
assert(not os.path.exists(SBOutputDir))
os.makedirs(os.path.join(SBOutputDir, LogFolderName))
@@ -384,8 +439,9 @@ def buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild):
runCleanupScript(Dir, PBuildLogFile)
normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode)
- print "Build complete (time: %.2f). See the log for more details: %s" % \
- ((time.time() - TBegin), BuildLogPath)
+ Local.stdout.write("Build complete (time: %.2f). "
+ "See the log for more details: %s\n" % (
+ (time.time() - TBegin), BuildLogPath))
def normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode):
@@ -456,14 +512,16 @@ def checkBuild(SBOutputDir):
CleanUpEmptyPlists(SBOutputDir)
CleanUpEmptyFolders(SBOutputDir)
Plists = glob.glob(SBOutputDir + "/*/*.plist")
- print "Number of bug reports (non-empty plist files) produced: %d" %\
- len(Plists)
+ Local.stdout.write(
+ "Number of bug reports (non-empty plist files) produced: %d\n" %
+ len(Plists))
return
- print "Error: analysis failed."
- print "Total of %d failures discovered." % TotalFailed
+ Local.stderr.write("Error: analysis failed.\n")
+ Local.stderr.write("Total of %d failures discovered.\n" % TotalFailed)
if TotalFailed > NumOfFailuresInSummary:
- print "See the first %d below.\n" % NumOfFailuresInSummary
+ Local.stderr.write(
+ "See the first %d below.\n" % NumOfFailuresInSummary)
# TODO: Add a line "See the results folder for more."
Idx = 0
@@ -471,9 +529,9 @@ def checkBuild(SBOutputDir):
if Idx >= NumOfFailuresInSummary:
break
Idx += 1
- print "\n-- Error #%d -----------\n" % Idx
+ Local.stderr.write("\n-- Error #%d -----------\n" % Idx)
with open(FailLogPathI, "r") as FailLogI:
- shutil.copyfileobj(FailLogI, sys.stdout)
+ shutil.copyfileobj(FailLogI, Local.stdout)
sys.exit(1)
@@ -526,25 +584,30 @@ def runCmpResults(Dir, Strictness=0):
assert(RefDir != NewDir)
if Verbose == 1:
- print " Comparing Results: %s %s" % (RefDir, NewDir)
+ Local.stdout.write(" Comparing Results: %s %s\n" % (
+ RefDir, NewDir))
PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
- Opts = CmpRuns.CmpOptions(rootA="", rootB=PatchedSourceDirPath)
+ Opts, Args = CmpRuns.generate_option_parser().parse_args(
+ ["--rootA", "", "--rootB", PatchedSourceDirPath])
# Scan the results, delete empty plist files.
NumDiffs, ReportsInRef, ReportsInNew = \
- CmpRuns.dumpScanBuildResultsDiff(RefDir, NewDir, Opts, False)
+ CmpRuns.dumpScanBuildResultsDiff(RefDir, NewDir, Opts,
+ deleteEmpty=False,
+ Stdout=Local.stdout)
if (NumDiffs > 0):
- print "Warning: %s differences in diagnostics." % NumDiffs
+ Local.stdout.write("Warning: %s differences in diagnostics.\n"
+ % NumDiffs)
if Strictness >= 2 and NumDiffs > 0:
- print "Error: Diffs found in strict mode (2)."
+ Local.stdout.write("Error: Diffs found in strict mode (2).\n")
TestsPassed = False
elif Strictness >= 1 and ReportsInRef != ReportsInNew:
- print "Error: The number of results are different in "\
- "strict mode (1)."
+ Local.stdout.write("Error: The number of results are different " +
+ " strict mode (1).\n")
TestsPassed = False
- print "Diagnostic comparison complete (time: %.2f)." % (
- time.time() - TBegin)
+ Local.stdout.write("Diagnostic comparison complete (time: %.2f).\n" % (
+ time.time() - TBegin))
return TestsPassed
@@ -564,19 +627,49 @@ def cleanupReferenceResults(SBOutputDir):
removeLogFile(SBOutputDir)
+class TestProjectThread(threading.Thread):
+ def __init__(self, TasksQueue, ResultsDiffer, FailureFlag):
+ """
+ :param ResultsDiffer: Used to signify that results differ from
+ the canonical ones.
+ :param FailureFlag: Used to signify a failure during the run.
+ """
+ self.TasksQueue = TasksQueue
+ self.ResultsDiffer = ResultsDiffer
+ self.FailureFlag = FailureFlag
+ super(TestProjectThread, self).__init__()
+
+ # Needed to gracefully handle interrupts with Ctrl-C
+ self.daemon = True
+
+ def run(self):
+ while not self.TasksQueue.empty():
+ try:
+ ProjArgs = self.TasksQueue.get()
+ Logger = logging.getLogger(ProjArgs[0])
+ Local.stdout = StreamToLogger(Logger, logging.INFO)
+ Local.stderr = StreamToLogger(Logger, logging.ERROR)
+ if not testProject(*ProjArgs):
+ self.ResultsDiffer.set()
+ self.TasksQueue.task_done()
+ except:
+ self.FailureFlag.set()
+ raise
+
+
def testProject(ID, ProjectBuildMode, IsReferenceBuild=False, Strictness=0):
"""
Test a given project.
:return TestsPassed: Whether tests have passed according
to the :param Strictness: criteria.
"""
- print " \n\n--- Building project %s" % (ID,)
+ Local.stdout.write(" \n\n--- Building project %s\n" % (ID,))
TBegin = time.time()
Dir = getProjectDir(ID)
if Verbose == 1:
- print " Build directory: %s." % (Dir,)
+ Local.stdout.write(" Build directory: %s.\n" % (Dir,))
# Set the build results directory.
RelOutputDir = getSBOutputDirName(IsReferenceBuild)
@@ -592,8 +685,8 @@ def testProject(ID, ProjectBuildMode, IsReferenceBuild=False, Strictness=0):
else:
TestsPassed = runCmpResults(Dir, Strictness)
- print "Completed tests for project %s (time: %.2f)." % \
- (ID, (time.time() - TBegin))
+ Local.stdout.write("Completed tests for project %s (time: %.2f).\n" % (
+ ID, (time.time() - TBegin)))
return TestsPassed
@@ -626,17 +719,62 @@ def validateProjectFile(PMapFile):
" (single file), 1 (project), or 2(single file c++11)."
raise Exception()
+def singleThreadedTestAll(ProjectsToTest):
+ """
+ Run all projects.
+ :return: whether tests have passed.
+ """
+ Success = True
+ for ProjArgs in ProjectsToTest:
+ Success &= testProject(*ProjArgs)
+ return Success
+
+def multiThreadedTestAll(ProjectsToTest, Jobs):
+ """
+ Run each project in a separate thread.
+
+ This is OK despite GIL, as testing is blocked
+ on launching external processes.
+
+ :return: whether tests have passed.
+ """
+ TasksQueue = Queue.Queue()
+
+ for ProjArgs in ProjectsToTest:
+ TasksQueue.put(ProjArgs)
+
+ ResultsDiffer = threading.Event()
+ FailureFlag = threading.Event()
+
+ for i in range(Jobs):
+ T = TestProjectThread(TasksQueue, ResultsDiffer, FailureFlag)
+ T.start()
+
+ # Required to handle Ctrl-C gracefully.
+ while TasksQueue.unfinished_tasks:
+ time.sleep(0.1) # Seconds.
+ if FailureFlag.is_set():
+ Local.stderr.write("Test runner crashed\n")
+ sys.exit(1)
+ return not ResultsDiffer.is_set()
+
+
+def testAll(Args):
+ ProjectsToTest = []
-def testAll(IsReferenceBuild=False, Strictness=0):
- TestsPassed = True
with projectFileHandler() as PMapFile:
validateProjectFile(PMapFile)
# Test the projects.
for (ProjName, ProjBuildMode) in iterateOverProjects(PMapFile):
- TestsPassed &= testProject(
- ProjName, int(ProjBuildMode), IsReferenceBuild, Strictness)
- return TestsPassed
+ ProjectsToTest.append((ProjName,
+ int(ProjBuildMode),
+ Args.regenerate,
+ Args.strictness))
+ if Args.jobs <= 1:
+ return singleThreadedTestAll(ProjectsToTest)
+ else:
+ return multiThreadedTestAll(ProjectsToTest, Args.jobs)
if __name__ == '__main__':
@@ -650,14 +788,12 @@ if __name__ == '__main__':
reference. Default is 0.')
Parser.add_argument('-r', dest='regenerate', action='store_true',
default=False, help='Regenerate reference output.')
+ Parser.add_argument('-j', '--jobs', dest='jobs', type=int,
+ default=0,
+ help='Number of projects to test concurrently')
Args = Parser.parse_args()
- IsReference = False
- Strictness = Args.strictness
- if Args.regenerate:
- IsReference = True
-
- TestsPassed = testAll(IsReference, Strictness)
+ TestsPassed = testAll(Args)
if not TestsPassed:
print "ERROR: Tests failed."
sys.exit(42)
diff --git a/utils/analyzer/SATestUpdateDiffs.py b/utils/analyzer/SATestUpdateDiffs.py
index 2282af15a523..92bbd83172ef 100755
--- a/utils/analyzer/SATestUpdateDiffs.py
+++ b/utils/analyzer/SATestUpdateDiffs.py
@@ -13,10 +13,10 @@ import sys
Verbose = 1
-def runCmd(Command):
+def runCmd(Command, **kwargs):
if Verbose:
print "Executing %s" % Command
- check_call(Command, shell=True)
+ check_call(Command, shell=True, **kwargs)
def updateReferenceResults(ProjName, ProjBuildMode):
@@ -34,27 +34,30 @@ def updateReferenceResults(ProjName, ProjBuildMode):
"previously run?"
sys.exit(1)
- # Remove reference results: in git, and then again for a good measure
- # with rm, as git might not remove things fully if there are empty
- # directories involved.
- runCmd('git rm -r -q "%s"' % (RefResultsPath,))
- runCmd('rm -rf "%s"' % (RefResultsPath,))
-
- # Replace reference results with a freshly computed once.
- runCmd('cp -r "%s" "%s"' % (CreatedResultsPath, RefResultsPath,))
-
- # Run cleanup script.
BuildLogPath = SATestBuild.getBuildLogPath(RefResultsPath)
+ Dirname = os.path.dirname(os.path.abspath(BuildLogPath))
+ runCmd("mkdir -p '%s'" % Dirname)
with open(BuildLogPath, "wb+") as PBuildLogFile:
+ # Remove reference results: in git, and then again for a good measure
+ # with rm, as git might not remove things fully if there are empty
+ # directories involved.
+ runCmd('git rm -r -q "%s"' % (RefResultsPath,), stdout=PBuildLogFile)
+ runCmd('rm -rf "%s"' % (RefResultsPath,), stdout=PBuildLogFile)
+
+ # Replace reference results with a freshly computed once.
+ runCmd('cp -r "%s" "%s"' % (CreatedResultsPath, RefResultsPath,),
+ stdout=PBuildLogFile)
+
+ # Run cleanup script.
SATestBuild.runCleanupScript(ProjDir, PBuildLogFile)
- SATestBuild.normalizeReferenceResults(
- ProjDir, RefResultsPath, ProjBuildMode)
+ SATestBuild.normalizeReferenceResults(
+ ProjDir, RefResultsPath, ProjBuildMode)
- # Clean up the generated difference results.
- SATestBuild.cleanupReferenceResults(RefResultsPath)
+ # Clean up the generated difference results.
+ SATestBuild.cleanupReferenceResults(RefResultsPath)
- runCmd('git add "%s"' % (RefResultsPath,))
+ runCmd('git add "%s"' % (RefResultsPath,), stdout=PBuildLogFile)
def main(argv):
diff --git a/utils/analyzer/SATestUtils.py b/utils/analyzer/SATestUtils.py
index 9220acc1bdbe..2320652619ed 100644
--- a/utils/analyzer/SATestUtils.py
+++ b/utils/analyzer/SATestUtils.py
@@ -37,18 +37,6 @@ def which(command, paths=None):
return None
-class flushfile(object):
- """
- Wrapper to flush the output after every print statement.
- """
- def __init__(self, f):
- self.f = f
-
- def write(self, x):
- self.f.write(x)
- self.f.flush()
-
-
def hasNoExtension(FileName):
(Root, Ext) = os.path.splitext(FileName)
return (Ext == "")
@@ -71,14 +59,15 @@ def getSDKPath(SDKName):
return check_output(Cmd, shell=True).rstrip()
-def runScript(ScriptPath, PBuildLogFile, Cwd):
+def runScript(ScriptPath, PBuildLogFile, Cwd, Stdout=sys.stdout,
+ Stderr=sys.stderr):
"""
Run the provided script if it exists.
"""
if os.path.exists(ScriptPath):
try:
if Verbose == 1:
- print " Executing: %s" % (ScriptPath,)
+ Stdout.write(" Executing: %s\n" % (ScriptPath,))
check_call("chmod +x '%s'" % ScriptPath, cwd=Cwd,
stderr=PBuildLogFile,
stdout=PBuildLogFile,
@@ -88,8 +77,8 @@ def runScript(ScriptPath, PBuildLogFile, Cwd):
stdout=PBuildLogFile,
shell=True)
except:
- print "Error: Running %s failed. See %s for details." % (
- ScriptPath, PBuildLogFile.name)
+ Stderr.write("Error: Running %s failed. See %s for details.\n" % (
+ ScriptPath, PBuildLogFile.name))
sys.exit(-1)
diff --git a/utils/bash-autocomplete.sh b/utils/bash-autocomplete.sh
index ed815d2d7ad1..bcda789b1c07 100755
--- a/utils/bash-autocomplete.sh
+++ b/utils/bash-autocomplete.sh
@@ -12,7 +12,7 @@ _clang()
{
local cur prev words cword arg flags w1 w2
# If latest bash-completion is not supported just initialize COMPREPLY and
- # initialize variables by setting manualy.
+ # initialize variables by setting manually.
_init_completion -n 2> /dev/null
if [[ "$?" != 0 ]]; then
COMPREPLY=()
@@ -25,39 +25,21 @@ _clang()
w2="${COMP_WORDS[$cword - 2]}"
fi
- # Clang want to know if -cc1 or -Xclang option is specified or not, because we don't want to show
- # cc1 options otherwise.
- if [[ "${COMP_WORDS[1]}" == "-cc1" || "$w1" == "-Xclang" ]]; then
- arg="#"
- fi
-
- # bash always separates '=' as a token even if there's no space before/after '='.
- # On the other hand, '=' is just a regular character for clang options that
- # contain '='. For example, "-stdlib=" is defined as is, instead of "-stdlib" and "=".
- # So, we need to partially undo bash tokenization here for integrity.
- if [[ "$cur" == -* ]]; then
- # -foo<tab>
- arg="$arg$cur"
- elif [[ "$w1" == -* && "$cur" == '=' ]]; then
- # -foo=<tab>
- arg="$arg$w1=,"
- elif [[ "$cur" == -*= ]]; then
- # -foo=<tab>
- arg="$arg$cur,"
- elif [[ "$w1" == -* ]]; then
- # -foo <tab> or -foo bar<tab>
- arg="$arg$w1,$cur"
- elif [[ "$w2" == -* && "$w1" == '=' ]]; then
- # -foo=bar<tab>
- arg="$arg$w2=,$cur"
- elif [[ ${cur: -1} != '=' && ${cur/=} != $cur ]]; then
- # -foo=bar<tab>
- arg="$arg${cur%=*}=,${cur#*=}"
- fi
+ # Pass all the current command-line flags to clang, so that clang can handle
+ # these internally.
+ # '=' is separated differently by bash, so we have to concat them without ','
+ for i in `seq 1 $cword`; do
+ if [[ $i == $cword || "${COMP_WORDS[$(($i+1))]}" == '=' ]]; then
+ arg="$arg${COMP_WORDS[$i]}"
+ else
+ arg="$arg${COMP_WORDS[$i]},"
+ fi
+ done
# expand ~ to $HOME
eval local path=${COMP_WORDS[0]}
- flags=$( "$path" --autocomplete="$arg" 2>/dev/null | sed -e 's/\t.*//' )
+ # Use $'\t' so that bash expands the \t for older versions of sed.
+ flags=$( "$path" --autocomplete="$arg" 2>/dev/null | sed -e $'s/\t.*//' )
# If clang is old that it does not support --autocomplete,
# fall back to the filename completion.
if [[ "$?" != 0 ]]; then
@@ -67,7 +49,7 @@ _clang()
# When clang does not emit any possible autocompletion, or user pushed tab after " ",
# just autocomplete files.
- if [[ "$flags" == "$(echo -e '\n')" || "$arg" == "" ]]; then
+ if [[ "$flags" == "$(echo -e '\n')" ]]; then
# If -foo=<tab> and there was no possible values, autocomplete files.
[[ "$cur" == '=' || "$cur" == -*= ]] && cur=""
_clang_filedir
diff --git a/utils/check_cfc/check_cfc.py b/utils/check_cfc/check_cfc.py
index c6ab9abf2352..7a739f46e308 100755
--- a/utils/check_cfc/check_cfc.py
+++ b/utils/check_cfc/check_cfc.py
@@ -237,7 +237,7 @@ def run_step(command, my_env, error_on_failure):
def get_temp_file_name(suffix):
"""Get a temporary file name with a particular suffix. Let the caller be
- reponsible for deleting it."""
+ responsible for deleting it."""
tf = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
tf.close()
return tf.name
diff --git a/utils/clangdiag.py b/utils/clangdiag.py
index f434bfeaa4c1..9a80e2696d52 100755
--- a/utils/clangdiag.py
+++ b/utils/clangdiag.py
@@ -107,7 +107,7 @@ def setDiagBreakpoint(frame, bp_loc, dict):
target = frame.GetThread().GetProcess().GetTarget()
diagtool = getDiagtool(target)
name = subprocess.check_output([diagtool, "find-diagnostic-id", id]).rstrip();
- # Make sure we only consider errors, warnings, and extentions.
+ # Make sure we only consider errors, warnings, and extensions.
# FIXME: Make this configurable?
prefixes = ['err_', 'warn_', 'exp_']
if len([prefix for prefix in prefixes+[''] if name.startswith(prefix)][0]):
@@ -124,7 +124,7 @@ def enable(exe_ctx, args):
numOfBreakpoints = target.GetNumBreakpoints()
if args.id:
- # Make sure we only consider errors, warnings, and extentions.
+ # Make sure we only consider errors, warnings, and extensions.
# FIXME: Make this configurable?
prefixes = ['err_', 'warn_', 'exp_']
if len([prefix for prefix in prefixes+[''] if args.id.startswith(prefix)][0]):
diff --git a/utils/find-unused-diagnostics.sh b/utils/find-unused-diagnostics.sh
index cd48e6920fb8..783f58ba1d30 100644
--- a/utils/find-unused-diagnostics.sh
+++ b/utils/find-unused-diagnostics.sh
@@ -8,7 +8,7 @@
ALL_DIAGS=$(grep -E --only-matching --no-filename '(err_|warn_|ext_|note_)[a-z_]+' ./include/clang/Basic/Diagnostic*.td)
# Now look for all potential identifiers in the source files.
-ALL_SOURCES=$(find lib include tools -name \*.cpp -or -name \*.h)
+ALL_SOURCES=$(find lib include tools utils -name \*.cpp -or -name \*.h)
DIAGS_IN_SOURCES=$(grep -E --only-matching --no-filename '(err_|warn_|ext_|note_)[a-z_]+' $ALL_SOURCES)
# Print all diags that occur in the .td files but not in the source.
diff --git a/utils/hmaptool/CMakeLists.txt b/utils/hmaptool/CMakeLists.txt
new file mode 100644
index 000000000000..5573009d343a
--- /dev/null
+++ b/utils/hmaptool/CMakeLists.txt
@@ -0,0 +1,16 @@
+set(CLANG_HMAPTOOL hmaptool)
+
+add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin/${CLANG_HMAPTOOL}
+ COMMAND ${CMAKE_COMMAND} -E make_directory
+ ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin
+ COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/${CLANG_HMAPTOOL}
+ ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin/
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${CLANG_HMAPTOOL})
+
+list(APPEND Depends ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin/${CLANG_HMAPTOOL})
+install(PROGRAMS ${CLANG_HMAPTOOL} DESTINATION bin)
+
+add_custom_target(hmaptool ALL DEPENDS ${Depends})
+set_target_properties(hmaptool PROPERTIES FOLDER "Utils")
+
diff --git a/utils/hmaptool/hmaptool b/utils/hmaptool/hmaptool
new file mode 100755
index 000000000000..2b1ca7436c3f
--- /dev/null
+++ b/utils/hmaptool/hmaptool
@@ -0,0 +1,296 @@
+#!/usr/bin/env python
+from __future__ import print_function
+
+import json
+import optparse
+import os
+import struct
+import sys
+
+###
+
+k_header_magic_LE = 'pamh'
+k_header_magic_BE = 'hmap'
+
+def hmap_hash(str):
+ """hash(str) -> int
+
+ Apply the "well-known" headermap hash function.
+ """
+
+ return sum((ord(c.lower()) * 13
+ for c in str), 0)
+
+class HeaderMap(object):
+ @staticmethod
+ def frompath(path):
+ with open(path, 'rb') as f:
+ magic = f.read(4)
+ if magic == k_header_magic_LE:
+ endian_code = '<'
+ elif magic == k_header_magic_BE:
+ endian_code = '>'
+ else:
+ raise SystemExit("error: %s: not a headermap" % (
+ path,))
+
+ # Read the header information.
+ header_fmt = endian_code + 'HHIIII'
+ header_size = struct.calcsize(header_fmt)
+ data = f.read(header_size)
+ if len(data) != header_size:
+ raise SystemExit("error: %s: truncated headermap header" % (
+ path,))
+
+ (version, reserved, strtable_offset, num_entries,
+ num_buckets, max_value_len) = struct.unpack(header_fmt, data)
+
+ if version != 1:
+ raise SystemExit("error: %s: unknown headermap version: %r" % (
+ path, version))
+ if reserved != 0:
+ raise SystemExit("error: %s: invalid reserved value in header" % (
+ path,))
+
+ # The number of buckets must be a power of two.
+ if num_buckets == 0 or (num_buckets & num_buckets - 1) != 0:
+ raise SystemExit("error: %s: invalid number of buckets" % (
+ path,))
+
+ # Read all of the buckets.
+ bucket_fmt = endian_code + 'III'
+ bucket_size = struct.calcsize(bucket_fmt)
+ buckets_data = f.read(num_buckets * bucket_size)
+ if len(buckets_data) != num_buckets * bucket_size:
+ raise SystemExit("error: %s: truncated headermap buckets" % (
+ path,))
+ buckets = [struct.unpack(bucket_fmt,
+ buckets_data[i*bucket_size:(i+1)*bucket_size])
+ for i in range(num_buckets)]
+
+ # Read the string table; the format doesn't explicitly communicate the
+ # size of the string table (which is dumb), so assume it is the rest of
+ # the file.
+ f.seek(0, 2)
+ strtable_size = f.tell() - strtable_offset
+ f.seek(strtable_offset)
+
+ if strtable_size == 0:
+ raise SystemExit("error: %s: unable to read zero-sized string table"%(
+ path,))
+ strtable = f.read(strtable_size)
+
+ if len(strtable) != strtable_size:
+ raise SystemExit("error: %s: unable to read complete string table"%(
+ path,))
+ if strtable[-1] != '\0':
+ raise SystemExit("error: %s: invalid string table in headermap" % (
+ path,))
+
+ return HeaderMap(num_entries, buckets, strtable)
+
+ def __init__(self, num_entries, buckets, strtable):
+ self.num_entries = num_entries
+ self.buckets = buckets
+ self.strtable = strtable
+
+ def get_string(self, idx):
+ if idx >= len(self.strtable):
+ raise SystemExit("error: %s: invalid string index" % (
+ path,))
+ end_idx = self.strtable.index('\0', idx)
+ return self.strtable[idx:end_idx]
+
+ @property
+ def mappings(self):
+ for key_idx,prefix_idx,suffix_idx in self.buckets:
+ if key_idx == 0:
+ continue
+ yield (self.get_string(key_idx),
+ self.get_string(prefix_idx) + self.get_string(suffix_idx))
+
+###
+
+def action_dump(name, args):
+ "dump a headermap file"
+
+ parser = optparse.OptionParser("%%prog %s [options] <headermap path>" % (
+ name,))
+ parser.add_option("-v", "--verbose", dest="verbose",
+ help="show more verbose output [%default]",
+ action="store_true", default=False)
+ (opts, args) = parser.parse_args(args)
+
+ if len(args) != 1:
+ parser.error("invalid number of arguments")
+
+ path, = args
+
+ hmap = HeaderMap.frompath(path)
+
+ # Dump all of the buckets.
+ print ('Header Map: %s' % (path,))
+ if opts.verbose:
+ print ('headermap: %r' % (path,))
+ print (' num entries: %d' % (hmap.num_entries,))
+ print (' num buckets: %d' % (len(hmap.buckets),))
+ print (' string table size: %d' % (len(hmap.strtable),))
+ for i,bucket in enumerate(hmap.buckets):
+ key_idx,prefix_idx,suffix_idx = bucket
+
+ if key_idx == 0:
+ continue
+
+ # Get the strings.
+ key = hmap.get_string(key_idx)
+ prefix = hmap.get_string(prefix_idx)
+ suffix = hmap.get_string(suffix_idx)
+
+ print (" bucket[%d]: %r -> (%r, %r) -- %d" % (
+ i, key, prefix, suffix, (hmap_hash(key) & (num_buckets - 1))))
+ else:
+ mappings = sorted(hmap.mappings)
+ for key,value in mappings:
+ print ("%s -> %s" % (key, value))
+ print ()
+
+def next_power_of_two(value):
+ if value < 0:
+ raise ArgumentError
+ return 1 if value == 0 else 2**(value - 1).bit_length()
+
+def action_write(name, args):
+ "write a headermap file from a JSON definition"
+
+ parser = optparse.OptionParser("%%prog %s [options] <input path> <output path>" % (
+ name,))
+ (opts, args) = parser.parse_args(args)
+
+ if len(args) != 2:
+ parser.error("invalid number of arguments")
+
+ input_path,output_path = args
+
+ with open(input_path, "r") as f:
+ input_data = json.load(f)
+
+ # Compute the headermap contents, we make a table that is 1/3 full.
+ mappings = input_data['mappings']
+ num_buckets = next_power_of_two(len(mappings) * 3)
+
+ table = [(0, 0, 0)
+ for i in range(num_buckets)]
+ max_value_len = 0
+ strtable = "\0"
+ for key,value in mappings.items():
+ if not isinstance(key, str):
+ key = key.decode('utf-8')
+ if not isinstance(value, str):
+ value = value.decode('utf-8')
+ max_value_len = max(max_value_len, len(value))
+
+ key_idx = len(strtable)
+ strtable += key + '\0'
+ prefix = os.path.dirname(value) + '/'
+ suffix = os.path.basename(value)
+ prefix_idx = len(strtable)
+ strtable += prefix + '\0'
+ suffix_idx = len(strtable)
+ strtable += suffix + '\0'
+
+ hash = hmap_hash(key)
+ for i in range(num_buckets):
+ idx = (hash + i) % num_buckets
+ if table[idx][0] == 0:
+ table[idx] = (key_idx, prefix_idx, suffix_idx)
+ break
+ else:
+ raise RuntimeError
+
+ endian_code = '<'
+ magic = k_header_magic_LE
+ magic_size = 4
+ header_fmt = endian_code + 'HHIIII'
+ header_size = struct.calcsize(header_fmt)
+ bucket_fmt = endian_code + 'III'
+ bucket_size = struct.calcsize(bucket_fmt)
+ strtable_offset = magic_size + header_size + num_buckets * bucket_size
+ header = (1, 0, strtable_offset, len(mappings),
+ num_buckets, max_value_len)
+
+ # Write out the headermap.
+ with open(output_path, 'wb') as f:
+ f.write(magic.encode())
+ f.write(struct.pack(header_fmt, *header))
+ for bucket in table:
+ f.write(struct.pack(bucket_fmt, *bucket))
+ f.write(strtable.encode())
+
+def action_tovfs(name, args):
+ "convert a headermap to a VFS layout"
+
+ parser = optparse.OptionParser("%%prog %s [options] <headermap path>" % (
+ name,))
+ parser.add_option("", "--build-path", dest="build_path",
+ help="build path prefix",
+ action="store", type=str)
+ (opts, args) = parser.parse_args(args)
+
+ if len(args) != 2:
+ parser.error("invalid number of arguments")
+ if opts.build_path is None:
+ parser.error("--build-path is required")
+
+ input_path,output_path = args
+
+ hmap = HeaderMap.frompath(input_path)
+
+ # Create the table for all the objects.
+ vfs = {}
+ vfs['version'] = 0
+ build_dir_contents = []
+ vfs['roots'] = [{
+ 'name' : opts.build_path,
+ 'type' : 'directory',
+ 'contents' : build_dir_contents }]
+
+ # We assume we are mapping framework paths, so a key of "Foo/Bar.h" maps to
+ # "<build path>/Foo.framework/Headers/Bar.h".
+ for key,value in hmap.mappings:
+ # If this isn't a framework style mapping, ignore it.
+ components = key.split('/')
+ if len(components) != 2:
+ continue
+ framework_name,header_name = components
+ build_dir_contents.append({
+ 'name' : '%s.framework/Headers/%s' % (framework_name,
+ header_name),
+ 'type' : 'file',
+ 'external-contents' : value })
+
+ with open(output_path, 'w') as f:
+ json.dump(vfs, f, indent=2)
+
+commands = dict((name[7:].replace("_","-"), f)
+ for name,f in locals().items()
+ if name.startswith('action_'))
+
+def usage():
+ print ("Usage: %s command [options]" % (
+ os.path.basename(sys.argv[0])), file=sys.stderr)
+ print (file=sys.stderr)
+ print ("Available commands:", file=sys.stderr)
+ cmds_width = max(map(len, commands))
+ for name,func in sorted(commands.items()):
+ print (" %-*s - %s" % (cmds_width, name, func.__doc__), file=sys.stderr)
+ sys.exit(1)
+
+def main():
+ if len(sys.argv) < 2 or sys.argv[1] not in commands:
+ usage()
+
+ cmd = sys.argv[1]
+ commands[cmd](cmd, sys.argv[2:])
+
+if __name__ == '__main__':
+ main()