diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2018-07-28 11:06:01 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2018-07-28 11:06:01 +0000 |
commit | 486754660bb926339aefcf012a3f848592babb8b (patch) | |
tree | ecdbc446c9876f4f120f701c243373cd3cb43db3 /utils | |
parent | 55e6d896ad333f07bb3b1ba487df214fc268a4ab (diff) | |
download | src-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.cpp | 297 | ||||
-rw-r--r-- | utils/TableGen/ClangCommentHTMLNamedCharacterReferenceEmitter.cpp | 2 | ||||
-rw-r--r-- | utils/TableGen/ClangDiagnosticsEmitter.cpp | 1083 | ||||
-rw-r--r-- | utils/TableGen/ClangOptionDocEmitter.cpp | 28 | ||||
-rw-r--r-- | utils/TableGen/ClangSACheckersEmitter.cpp | 2 | ||||
-rw-r--r-- | utils/TableGen/NeonEmitter.cpp | 180 | ||||
-rw-r--r-- | utils/TableGen/TableGen.cpp | 5 | ||||
-rw-r--r-- | utils/TableGen/TableGenBackends.h | 1 | ||||
-rwxr-xr-x | utils/analyzer/CmpRuns.py | 191 | ||||
-rw-r--r-- | utils/analyzer/SATestAdd.py | 4 | ||||
-rw-r--r-- | utils/analyzer/SATestBuild.py | 272 | ||||
-rwxr-xr-x | utils/analyzer/SATestUpdateDiffs.py | 37 | ||||
-rw-r--r-- | utils/analyzer/SATestUtils.py | 21 | ||||
-rwxr-xr-x | utils/bash-autocomplete.sh | 46 | ||||
-rwxr-xr-x | utils/check_cfc/check_cfc.py | 2 | ||||
-rwxr-xr-x | utils/clangdiag.py | 4 | ||||
-rw-r--r-- | utils/find-unused-diagnostics.sh | 2 | ||||
-rw-r--r-- | utils/hmaptool/CMakeLists.txt | 16 | ||||
-rwxr-xr-x | utils/hmaptool/hmaptool | 296 |
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() |