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-repeated-fixed.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/parser-versioninfo-repeated-fixed.rc @@ -0,0 +1,6 @@ +1 VERSIONINFO +FileVersion 1, 2, 3, 4 +PRODUCTVERSION 5, 6, 7, 8 +FILEVERSION 9, 10, 11, 12 +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: FILEFLAGSMASK: 50 +; PGOOD-NEXT: Fixed: FILEFLAGS: 555 +; PGOOD-NEXT: Fixed: FILEOS: 110 +; PGOOD-NEXT: Fixed: FILETYPE: 555555 +; PGOOD-NEXT: Fixed: FILESUBTYPE: 14 +; 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,33 @@ ; 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 + + +; RUN: not llvm-rc /V %p/Inputs/parser-versioninfo-repeated-fixed.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO6 + +; PVERSIONINFO6: llvm-rc: Error parsing file: expected yet unread fixed VERSIONINFO statement type, got FILEVERSION Index: llvm/tools/llvm-rc/ResourceScriptParser.h =================================================================== --- llvm/tools/llvm-rc/ResourceScriptParser.h +++ llvm/tools/llvm-rc/ResourceScriptParser.h @@ -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); @@ -322,6 +324,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: @@ -446,6 +455,72 @@ 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()); + auto FixedType = RetType::getFixedType(*TypeResult); + + if (!RetType::isTypeSupported(FixedType)) + return getExpectedError("fixed VERSIONINFO statement type", true); + if (Result.IsTypePresent[FixedType]) + return getExpectedError("yet unread fixed VERSIONINFO statement type", + true); + + // VERSION variations take multiple integers. + size_t NumInts = RetType::isVersionType(FixedType) ? 4 : 1; + ASSIGN_OR_RETURN(ArgsResult, readIntsWithCommas(NumInts, NumInts)); + Result.setValue(FixedType, *ArgsResult); + } + + 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,106 @@ 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 { + public: + enum VersionInfoFixedType { + FtUnknown, + FtFileVersion, + FtProductVersion, + FtFileFlagsMask, + FtFileFlags, + FtFileOS, + FtFileType, + FtFileSubtype, + FtNumTypes + }; + + private: + static const StringMap FixedFieldsInfoMap; + static const StringRef FixedFieldsNames[FtNumTypes]; + + public: + SmallVector FixedInfo[FtNumTypes]; + SmallVector IsTypePresent; + + static VersionInfoFixedType getFixedType(StringRef Type); + static bool isTypeSupported(VersionInfoFixedType Type); + static bool isVersionType(VersionInfoFixedType Type); + + VersionInfoFixed() : IsTypePresent(FtNumTypes, false) {} + + void setValue(VersionInfoFixedType Type, ArrayRef Value) { + FixedInfo[Type] = SmallVector(Value.begin(), Value.end()); + IsTypePresent[Type] = true; + } + + 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,78 @@ 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; +using VersionInfoFixedType = VersionInfoFixed::VersionInfoFixedType; + +const StringRef + VersionInfoFixed::FixedFieldsNames[VersionInfoFixed::FtNumTypes] = { + "", "FILEVERSION", "PRODUCTVERSION", "FILEFLAGSMASK", + "FILEFLAGS", "FILEOS", "FILETYPE", "FILESUBTYPE"}; + +const StringMap VersionInfoFixed::FixedFieldsInfoMap = { + {FixedFieldsNames[FtFileVersion], FtFileVersion}, + {FixedFieldsNames[FtProductVersion], FtProductVersion}, + {FixedFieldsNames[FtFileFlagsMask], FtFileFlagsMask}, + {FixedFieldsNames[FtFileFlags], FtFileFlags}, + {FixedFieldsNames[FtFileOS], FtFileOS}, + {FixedFieldsNames[FtFileType], FtFileType}, + {FixedFieldsNames[FtFileSubtype], FtFileSubtype}}; + +VersionInfoFixedType VersionInfoFixed::getFixedType(StringRef Type) { + auto UpperType = Type.upper(); + auto Iter = FixedFieldsInfoMap.find(UpperType); + if (Iter != FixedFieldsInfoMap.end()) + return Iter->getValue(); + return FtUnknown; +} + +bool VersionInfoFixed::isTypeSupported(VersionInfoFixedType Type) { + return FtUnknown < Type && Type < FtNumTypes; +} + +bool VersionInfoFixed::isVersionType(VersionInfoFixedType Type) { + switch (Type) { + case FtFileVersion: + case FtProductVersion: + return true; + + default: + return false; + } +} + +raw_ostream &VersionInfoFixed::log(raw_ostream &OS) const { + for (int Type = FtUnknown; Type < FtNumTypes; ++Type) { + if (!isTypeSupported((VersionInfoFixedType)Type)) + continue; + OS << " Fixed: " << FixedFieldsNames[Type] << ":"; + for (uint32_t Val : FixedInfo[Type]) + OS << " " << Val; + OS << "\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"; }