Index: llvm/test/tools/llvm-rc/Inputs/parser-correct-everything.rc =================================================================== --- llvm/test/tools/llvm-rc/Inputs/parser-correct-everything.rc +++ llvm/test/tools/llvm-rc/Inputs/parser-correct-everything.rc @@ -78,3 +78,36 @@ END 26 DIALOGEX 1, 2, 3, 4 {} + +1 VERSIONINFO +FILEVERSION 1, 2, 3, 4 +PRODUCTVERSION 5, 6, 7, 8 +FILEFLAGSMASK 50 +FILEFLAGS 555 +FILEOS 110 +FILETYPE 555555 +FILESUBTYPE 14 +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + { + VALUE "CompanyName", "a" + VALUE "FileDescription", "b" + VALUE "FileVersion", "c" + VALUE "InternalName", "d" + VALUE "LegalCopyright", "e" + VALUE "LegalTrademarks1", "f" + VALUE "LegalTrademarks2", "g" + VALUE "OriginalFilename", L"h" + VALUE "ProductName", "ii", 2, 3 + VALUE "ProductVersion" + } + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + + END +END Index: llvm/test/tools/llvm-rc/Inputs/parser-versioninfo-bad-type.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/parser-versioninfo-bad-type.rc @@ -0,0 +1,4 @@ +1 VERSIONINFO +BEGIN + INCORRECT "1", "2" +END Index: llvm/test/tools/llvm-rc/Inputs/parser-versioninfo-named-main-block.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/parser-versioninfo-named-main-block.rc @@ -0,0 +1,4 @@ +1 VERSIONINFO +BLOCK "hello" +BEGIN +END Index: llvm/test/tools/llvm-rc/Inputs/parser-versioninfo-unnamed-inner-block.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/parser-versioninfo-unnamed-inner-block.rc @@ -0,0 +1,4 @@ +1 VERSIONINFO +BEGIN + BLOCK {} +END Index: llvm/test/tools/llvm-rc/Inputs/parser-versioninfo-unnamed-value.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/parser-versioninfo-unnamed-value.rc @@ -0,0 +1,7 @@ +1 VERSIONINFO +BEGIN + BLOCK "VarFileInfo" + BEGIN + VALUE + END +END Index: llvm/test/tools/llvm-rc/Inputs/parser-versioninfo-wrong-fixed.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/parser-versioninfo-wrong-fixed.rc @@ -0,0 +1,3 @@ +1 VERSIONINFO +WEIRDFIXED 5 +BEGIN END Index: llvm/test/tools/llvm-rc/parser.test =================================================================== --- llvm/test/tools/llvm-rc/parser.test +++ llvm/test/tools/llvm-rc/parser.test @@ -64,6 +64,34 @@ ; PGOOD-NEXT: Control (5): EDITTEXT, title: , loc: (1, 2), size: [4, 7], style: 8 ; PGOOD-NEXT: Dialog (25): loc: (1, 2), size: [3, 4], help ID: 0 ; PGOOD-NEXT: DialogEx (26): loc: (1, 2), size: [3, 4], help ID: 0 +; PGOOD-NEXT: VersionInfo (1): +; PGOOD-NEXT: Fixed: FILEVERSION: 1 2 3 4 +; PGOOD-NEXT: Fixed: PRODUCTVERSION: 5 6 7 8 +; PGOOD-NEXT: Fixed: FILEOS: 110 +; PGOOD-NEXT: Fixed: FILETYPE: 555555 +; PGOOD-NEXT: Fixed: FILEFLAGSMASK: 50 +; PGOOD-NEXT: Fixed: FILESUBTYPE: 14 +; PGOOD-NEXT: Fixed: FILEFLAGS: 555 +; PGOOD-NEXT: Start of block (name: ) +; PGOOD-NEXT: Start of block (name: "StringFileInfo") +; PGOOD-NEXT: Start of block (name: "040904E4") +; PGOOD-NEXT: "CompanyName" => "a" +; PGOOD-NEXT: "FileDescription" => "b" +; PGOOD-NEXT: "FileVersion" => "c" +; PGOOD-NEXT: "InternalName" => "d" +; PGOOD-NEXT: "LegalCopyright" => "e" +; PGOOD-NEXT: "LegalTrademarks1" => "f" +; PGOOD-NEXT: "LegalTrademarks2" => "g" +; PGOOD-NEXT: "OriginalFilename" => L"h" +; PGOOD-NEXT: "ProductName" => "ii" 2 3 +; PGOOD-NEXT: "ProductVersion" => +; PGOOD-NEXT: End of block +; PGOOD-NEXT: End of block +; PGOOD-NEXT: Start of block (name: "VarFileInfo") +; PGOOD-NEXT: "Translation" => 1033 1252 +; PGOOD-NEXT: End of block +; PGOOD-NEXT: End of block + ; RUN: not llvm-rc /V %p/Inputs/parser-stringtable-no-string.rc 2>&1 | FileCheck %s --check-prefix PSTRINGTABLE1 @@ -184,3 +212,28 @@ ; RUN: not llvm-rc /V %p/Inputs/parser-dialog-unnecessary-string.rc 2>&1 | FileCheck %s --check-prefix PDIALOG5 ; PDIALOG5: llvm-rc: Error parsing file: expected integer, got "This shouldn't be here" + + +; RUN: not llvm-rc /V %p/Inputs/parser-versioninfo-wrong-fixed.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO1 + +; PVERSIONINFO1: llvm-rc: Error parsing file: expected fixed VERSIONINFO statement type, got WEIRDFIXED + + +; RUN: not llvm-rc /V %p/Inputs/parser-versioninfo-named-main-block.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO2 + +; PVERSIONINFO2: llvm-rc: Error parsing file: expected fixed VERSIONINFO statement type, got BLOCK + + +; RUN: not llvm-rc /V %p/Inputs/parser-versioninfo-unnamed-inner-block.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO3 + +; PVERSIONINFO3: llvm-rc: Error parsing file: expected string, got { + + +; RUN: not llvm-rc /V %p/Inputs/parser-versioninfo-unnamed-value.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO4 + +; PVERSIONINFO4: llvm-rc: Error parsing file: expected string, got END + + +; RUN: not llvm-rc /V %p/Inputs/parser-versioninfo-bad-type.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO5 + +; PVERSIONINFO5: llvm-rc: Error parsing file: expected BLOCK or VALUE, got INCORRECT Index: llvm/tools/llvm-rc/ResourceScriptParser.h =================================================================== --- llvm/tools/llvm-rc/ResourceScriptParser.h +++ llvm/tools/llvm-rc/ResourceScriptParser.h @@ -103,7 +103,7 @@ // flag (0-based) has been read, the i-th bit of the result is set. // As long as parser has a comma to read, it expects to be fed with // a correct flag afterwards. - Expected parseFlags(ArrayRef FlagDesc); + Expected parseFlags(const StringRef FlagDesc[], size_t NumFlags); // Reads a set of optional statements. These can change the behavior of // a number of resource types (e.g. STRINGTABLE, MENU or DIALOG) if provided @@ -133,6 +133,7 @@ ParseType parseHTMLResource(); ParseType parseMenuResource(); ParseType parseStringTableResource(); + ParseType parseVersionInfoResource(); // Helper DIALOG parser - a single control. Expected parseControl(); @@ -140,6 +141,15 @@ // Helper MENU parser. Expected parseMenuItemsList(); + // Helper VERSIONINFO parser - read the contents of a single BLOCK statement, + // from BEGIN to END. + Expected> + parseVersionInfoBlockContents(StringRef BlockName); + // Helper VERSIONINFO parser - read either VALUE or BLOCK statement. + Expected> parseVersionInfoStmt(); + // Helper VERSIONINFO parser - read fixed VERSIONINFO statements. + Expected parseVersionInfoFixed(); + // Optional statement parsers. ParseOptionType parseLanguageStmt(); ParseOptionType parseCharacteristicsStmt(); Index: llvm/tools/llvm-rc/ResourceScriptParser.cpp =================================================================== --- llvm/tools/llvm-rc/ResourceScriptParser.cpp +++ llvm/tools/llvm-rc/ResourceScriptParser.cpp @@ -77,6 +77,8 @@ Result = parseHTMLResource(); else if (TypeToken->equalsLower("MENU")) Result = parseMenuResource(); + else if (TypeToken->equalsLower("VERSIONINFO")) + Result = parseVersionInfoResource(); else return getExpectedError("resource type", /* IsAlreadyRead = */ true); @@ -199,27 +201,32 @@ return std::move(Result); } -Expected RCParser::parseFlags(ArrayRef FlagDesc) { - assert(FlagDesc.size() <= 32 && "More than 32 flags won't fit in result."); - assert(!FlagDesc.empty()); +Expected RCParser::parseFlags(const StringRef FlagDesc[], + size_t NumFlags) { + assert(NumFlags <= 32 && "More than 32 flags won't fit in result."); + assert(NumFlags > 0); uint32_t Result = 0; while (isNextTokenKind(Kind::Comma)) { consume(); ASSIGN_OR_RETURN(FlagResult, readIdentifier()); - bool FoundFlag = false; - for (size_t FlagId = 0; FlagId < FlagDesc.size(); ++FlagId) { - if (!FlagResult->equals_lower(FlagDesc[FlagId])) - continue; + bool FoundFlag = false; - Result |= (1U << FlagId); - FoundFlag = true; - break; + for (size_t FlagId = 0; FlagId < NumFlags; ++FlagId) { + if (FlagResult->equals_lower(FlagDesc[FlagId])) { + Result |= (1U << FlagId); + FoundFlag = true; + break; + } } - if (!FoundFlag) - return getExpectedError(join(FlagDesc, "/"), true); + if (!FoundFlag) { + std::string ExpectedList = FlagDesc[0]; + for (size_t FlagId = 1; FlagId < NumFlags; ++FlagId) + ExpectedList += ("/" + FlagDesc[FlagId]).str(); + return getExpectedError(ExpectedList, true); + } } return Result; @@ -256,7 +263,7 @@ if (TypeToken->equals_lower("STYLE")) return parseStyleStmt(); } - + return getExpectedError("optional statement type, BEGIN or '{'", /* IsAlreadyRead = */ true); } @@ -278,7 +285,8 @@ RETURN_IF_ERROR(consumeType(Kind::Comma)); ASSIGN_OR_RETURN(IDResult, readInt()); ASSIGN_OR_RETURN(FlagsResult, - parseFlags(AcceleratorsResource::Accelerator::OptionsStr)); + parseFlags(AcceleratorsResource::Accelerator::OptionsStr, + AcceleratorsResource::Accelerator::NumFlags)); Accels->addAccelerator(*EventResult, *IDResult, *FlagsResult); } @@ -322,6 +330,13 @@ return std::move(Dialog); } +RCParser::ParseType RCParser::parseVersionInfoResource() { + ASSIGN_OR_RETURN(FixedResult, parseVersionInfoFixed()); + ASSIGN_OR_RETURN(BlockResult, parseVersionInfoBlockContents(StringRef())); + return make_unique(std::move(**BlockResult), + std::move(*FixedResult)); +} + Expected RCParser::parseControl() { // Each control definition (except CONTROL) follows one of the schemes below // depending on the control class: @@ -329,7 +344,7 @@ // [class] id, x, y, width, height [, style] [, exstyle] [, helpID] // Note that control ids must be integers. ASSIGN_OR_RETURN(ClassResult, readIdentifier()); - std::string ClassUpper = ClassResult->upper(); + StringRef ClassUpper = ClassResult->upper(); if (Control::SupportedCtls.find(ClassUpper) == Control::SupportedCtls.end()) return getExpectedError("control type, END or '}'", true); @@ -393,7 +408,7 @@ List.addDefinition(make_unique()); continue; } - + return getExpectedError("SEPARATOR or string", true); } @@ -409,7 +424,8 @@ MenuResult = *IntResult; } - ASSIGN_OR_RETURN(FlagsResult, parseFlags(MenuDefinition::OptionsStr)); + ASSIGN_OR_RETURN(FlagsResult, parseFlags(MenuDefinition::OptionsStr, + MenuDefinition::NumFlags)); if (IsPopup) { // If POPUP, read submenu items recursively. @@ -446,6 +462,74 @@ return std::move(Table); } +Expected> +RCParser::parseVersionInfoBlockContents(StringRef BlockName) { + RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); + + auto Contents = make_unique(BlockName); + + while (!isNextTokenKind(Kind::BlockEnd)) { + ASSIGN_OR_RETURN(Stmt, parseVersionInfoStmt()); + Contents->addStmt(std::move(*Stmt)); + } + + consume(); // Consume BlockEnd. + + return std::move(Contents); +} + +Expected> RCParser::parseVersionInfoStmt() { + // Expect either BLOCK or VALUE, then a name or a key (a string). + ASSIGN_OR_RETURN(TypeResult, readIdentifier()); + + if (TypeResult->equals_lower("BLOCK")) { + ASSIGN_OR_RETURN(NameResult, readString()); + return parseVersionInfoBlockContents(*NameResult); + } + + if (TypeResult->equals_lower("VALUE")) { + ASSIGN_OR_RETURN(KeyResult, readString()); + // Read a (possibly empty) list of strings and/or ints, each preceded by + // a comma. + std::vector Values; + + while (consumeOptionalType(Kind::Comma)) { + ASSIGN_OR_RETURN(ValueResult, readIntOrString()); + Values.push_back(*ValueResult); + } + return make_unique(*KeyResult, std::move(Values)); + } + + return getExpectedError("BLOCK or VALUE", true); +} + +Expected +RCParser::parseVersionInfoFixed() { + using RetType = VersionInfoResource::VersionInfoFixed; + RetType Result; + + // Read until the beginning of the block. + while (!isNextTokenKind(Kind::BlockBegin)) { + ASSIGN_OR_RETURN(TypeResult, readIdentifier()); + + if (!RetType::isTypeSupported(*TypeResult)) + return getExpectedError("fixed VERSIONINFO statement type", true); + + if (RetType::isVersionType(*TypeResult)) { + // VERSION variations take four integers. + ASSIGN_OR_RETURN(ArgsResult, readIntsWithCommas(4, 4)); + Result.setValue(*TypeResult, *ArgsResult); + continue; + } + + // Other ones take exactly one integer. + ASSIGN_OR_RETURN(ArgResult, readInt()); + Result.setValue(*TypeResult, *ArgResult); + } + + return Result; +} + RCParser::ParseOptionType RCParser::parseLanguageStmt() { ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 2, /* max = */ 2)); return make_unique((*Args)[0], (*Args)[1]); Index: llvm/tools/llvm-rc/ResourceScriptStmt.h =================================================================== --- llvm/tools/llvm-rc/ResourceScriptStmt.h +++ llvm/tools/llvm-rc/ResourceScriptStmt.h @@ -319,6 +319,92 @@ raw_ostream &log(raw_ostream &) const override; }; +// -- VERSIONINFO resource and its helper classes -- +// +// This resource lists the version information on the executable/library. +// The declaration consists of the following items: +// * A number of fixed optional version statements (e.g. FILEVERSION, FILEOS) +// * BEGIN +// * A number of BLOCK and/or VALUE statements. BLOCK recursively defines +// another block of version information, whereas VALUE defines a +// key -> value correspondence. There might be more than one value +// corresponding to the single key. +// * END +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381058(v=vs.85).aspx + +// A single VERSIONINFO statement; +class VersionInfoStmt { +public: + virtual raw_ostream &log(raw_ostream &OS) const { return OS << "VI stmt\n"; } + virtual ~VersionInfoStmt() {} +}; + +// BLOCK definition; also the main VERSIONINFO declaration is considered a +// BLOCK, although it has no name. +// The correct top-level blocks are "VarFileInfo" and "StringFileInfo". We don't +// care about them at the parsing phase. +class VersionInfoBlock : public VersionInfoStmt { + std::vector> Stmts; + StringRef Name; + +public: + VersionInfoBlock(StringRef BlockName) : Name(BlockName) {} + void addStmt(std::unique_ptr Stmt) { + Stmts.push_back(std::move(Stmt)); + } + raw_ostream &log(raw_ostream &) const override; +}; + +class VersionInfoValue : public VersionInfoStmt { + StringRef Key; + std::vector Values; + +public: + VersionInfoValue(StringRef InfoKey, std::vector &&Vals) + : Key(InfoKey), Values(std::move(Vals)) {} + raw_ostream &log(raw_ostream &) const override; +}; + +class VersionInfoResource : public RCResource { +public: + // A class listing fixed VERSIONINFO statements (occuring before main BEGIN). + // If any of these is not specified, it is assumed by the original tool to + // be equal to 0. + class VersionInfoFixed { + static const StringSet<> SupportedTypes; + static const StringSet<> VersionTypes; + + public: + StringMap> VersionData; + StringMap FlagsData; + + static bool isTypeSupported(StringRef Type); + static bool isVersionType(StringRef Type); + + VersionInfoFixed() {} + + template + void setValue(StringRef Name, const SmallVector &Value) { + VersionData[Name] = SmallVector(Value.begin(), Value.end()); + } + void setValue(StringRef Name, uint32_t Value) { FlagsData[Name] = Value; } + + raw_ostream &log(raw_ostream &) const; + }; + +private: + VersionInfoBlock MainBlock; + VersionInfoFixed FixedData; + +public: + VersionInfoResource(VersionInfoBlock &&TopLevelBlock, + VersionInfoFixed &&FixedInfo) + : MainBlock(std::move(TopLevelBlock)), FixedData(std::move(FixedInfo)) {} + + raw_ostream &log(raw_ostream &) const override; +}; + // CHARACTERISTICS optional statement. // // Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380872(v=vs.85).aspx Index: llvm/tools/llvm-rc/ResourceScriptStmt.cpp =================================================================== --- llvm/tools/llvm-rc/ResourceScriptStmt.cpp +++ llvm/tools/llvm-rc/ResourceScriptStmt.cpp @@ -142,6 +142,55 @@ return OS; } +raw_ostream &VersionInfoBlock::log(raw_ostream &OS) const { + OS << " Start of block (name: " << Name << ")\n"; + for (auto &Stmt : Stmts) + Stmt->log(OS); + return OS << " End of block\n"; +} + +raw_ostream &VersionInfoValue::log(raw_ostream &OS) const { + OS << " " << Key << " =>"; + for (auto &Value : Values) + OS << " " << Value; + return OS << "\n"; +} + +using VersionInfoFixed = VersionInfoResource::VersionInfoFixed; + +const StringSet<> VersionInfoFixed::SupportedTypes = { + "FILEVERSION", "PRODUCTVERSION", "FILEFLAGSMASK", "FILEFLAGS", + "FILEOS", "FILETYPE", "FILESUBTYPE"}; + +const StringSet<> VersionInfoFixed::VersionTypes = {"FILEVERSION", + "PRODUCTVERSION"}; + +bool VersionInfoFixed::isTypeSupported(StringRef Type) { + return SupportedTypes.find(Type.upper()) != SupportedTypes.end(); +} + +bool VersionInfoFixed::isVersionType(StringRef Type) { + return VersionTypes.find(Type.upper()) != VersionTypes.end(); +} + +raw_ostream &VersionInfoFixed::log(raw_ostream &OS) const { + for (auto &Info : VersionData) { + OS << " Fixed: " << Info.getKey() << ":"; + for (uint32_t Val : Info.getValue()) + OS << " " << Val; + OS << "\n"; + } + for (auto &Info : FlagsData) + OS << " Fixed: " << Info.getKey() << ": " << Info.getValue() << "\n"; + return OS; +} + +raw_ostream &VersionInfoResource::log(raw_ostream &OS) const { + OS << "VersionInfo (" << ResName << "):\n"; + FixedData.log(OS); + return MainBlock.log(OS); +} + raw_ostream &CharacteristicsStmt::log(raw_ostream &OS) const { return OS << "Characteristics: " << Value << "\n"; }