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 @@ -93,15 +93,15 @@ BLOCK "040904E4" { VALUE "CompanyName", "a" - VALUE "FileDescription", "b" + VALUE "FileDescription", "b" "c" "d", 1 3 7L, "y", "h" "d" VALUE "FileVersion", "c" VALUE "InternalName", "d" - VALUE "LegalCopyright", "e" - VALUE "LegalTrademarks1", "f" + VALUE "LegalCopyright", "e" 0 + VALUE "LegalTrademarks1", 1 2, 3 VALUE "LegalTrademarks2", "g" VALUE "OriginalFilename", L"h" - VALUE "ProductName", "ii", 2, 3 - VALUE "ProductVersion" + VALUE "ProductName", "ii", 2L, 3 + VALUE "ProductVersion", 0x12345678L } END Index: llvm/test/tools/llvm-rc/Inputs/tag-versioninfo-mixed-ints-strings.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-versioninfo-mixed-ints-strings.rc @@ -0,0 +1,18 @@ +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", 32768 + VALUE "FileDescription", 5, "a", 3 + } + END +END Index: llvm/test/tools/llvm-rc/Inputs/tag-versioninfo-word-too-large.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-versioninfo-word-too-large.rc @@ -0,0 +1,18 @@ +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", 32768 + VALUE "FileDescription", 65536 + } + END +END Index: llvm/test/tools/llvm-rc/Inputs/tag-versioninfo.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-versioninfo.rc @@ -0,0 +1,32 @@ +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" "c", "d", L"eee" "f" L"g", "a", L"hohoho" + VALUE "FileVersion", "c" + VALUE "InternalName", "d" + VALUE "LegalCopyright", "e" "0" + VALUE "LegalTrademarks1", 1 2, 3 + VALUE "LegalTrademarks2", "g" + VALUE "OriginalFilename", L"h" + VALUE "ProductName", "a" "b", "c" + VALUE "ProductVersion", 0x12345678L + } + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + + END +END Index: llvm/test/tools/llvm-rc/parser.test =================================================================== --- llvm/test/tools/llvm-rc/parser.test +++ llvm/test/tools/llvm-rc/parser.test @@ -76,19 +76,19 @@ ; PGOOD-NEXT: Start of block (name: "StringFileInfo") ; PGOOD-NEXT: Start of block (name: "040904E4") ; PGOOD-NEXT: "CompanyName" => "a" -; PGOOD-NEXT: "FileDescription" => "b" +; PGOOD-NEXT: "FileDescription" => "b" "c" "d", 1 3 7L, "y", "h" "d" ; PGOOD-NEXT: "FileVersion" => "c" ; PGOOD-NEXT: "InternalName" => "d" ; PGOOD-NEXT: "LegalCopyright" => "e" -; PGOOD-NEXT: "LegalTrademarks1" => "f" +; PGOOD-NEXT: "LegalTrademarks1" => 1 2, 3 ; PGOOD-NEXT: "LegalTrademarks2" => "g" ; PGOOD-NEXT: "OriginalFilename" => L"h" -; PGOOD-NEXT: "ProductName" => "ii" 2 3 -; PGOOD-NEXT: "ProductVersion" => +; PGOOD-NEXT: "ProductName" => "ii", 2L, 3 +; PGOOD-NEXT: "ProductVersion" => 305419896L ; PGOOD-NEXT: End of block ; PGOOD-NEXT: End of block ; PGOOD-NEXT: Start of block (name: "VarFileInfo") -; PGOOD-NEXT: "Translation" => 1033 1252 +; PGOOD-NEXT: "Translation" => 1033, 1252 ; PGOOD-NEXT: End of block ; PGOOD-NEXT: End of block ; PGOOD-NEXT: User-defined (type: MYTYPE, name: MYNAME): "filename" Index: llvm/test/tools/llvm-rc/tag-versioninfo.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/tag-versioninfo.test @@ -0,0 +1,66 @@ +; RUN: llvm-rc /FO %t %p/Inputs/tag-versioninfo.rc +; RUN: llvm-readobj %t | FileCheck %s + +; CHECK: Resource type (int): 16 +; CHECK-NEXT: Resource name (int): 1 +; CHECK-NEXT: Data version: 0 +; CHECK-NEXT: Memory flags: 0x30 +; CHECK-NEXT: Language ID: 1033 +; CHECK-NEXT: Version (major): 0 +; CHECK-NEXT: Version (minor): 0 +; CHECK-NEXT: Characteristics: 0 +; CHECK-NEXT: Data size: 672 +; CHECK-NEXT: Data: ( +; CHECK-NEXT: 0000: A0023400 00005600 53005F00 56004500 |..4...V.S._.V.E.| +; CHECK-NEXT: 0010: 52005300 49004F00 4E005F00 49004E00 |R.S.I.O.N._.I.N.| +; CHECK-NEXT: 0020: 46004F00 00000000 BD04EFFE 00000100 |F.O.............| +; CHECK-NEXT: 0030: 02000100 04000300 06000500 08000700 |................| +; CHECK-NEXT: 0040: 32000000 2B020000 6E000000 237A0800 |2...+...n...#z..| +; CHECK-NEXT: 0050: 0E000000 00000000 00000000 00020000 |................| +; CHECK-NEXT: 0060: 01005300 74007200 69006E00 67004600 |..S.t.r.i.n.g.F.| +; CHECK-NEXT: 0070: 69006C00 65004900 6E006600 6F000000 |i.l.e.I.n.f.o...| +; CHECK-NEXT: 0080: DC010000 01003000 34003000 39003000 |......0.4.0.9.0.| +; CHECK-NEXT: 0090: 34004500 34000000 24000200 01004300 |4.E.4...$.....C.| +; CHECK-NEXT: 00A0: 6F006D00 70006100 6E007900 4E006100 |o.m.p.a.n.y.N.a.| +; CHECK-NEXT: 00B0: 6D006500 00000000 61000000 50001400 |m.e.....a...P...| +; CHECK-NEXT: 00C0: 01004600 69006C00 65004400 65007300 |..F.i.l.e.D.e.s.| +; CHECK-NEXT: 00D0: 63007200 69007000 74006900 6F006E00 |c.r.i.p.t.i.o.n.| +; CHECK-NEXT: 00E0: 00000000 62006300 00006400 00006500 |....b.c...d...e.| +; CHECK-NEXT: 00F0: 65006500 66006700 00006100 00006800 |e.e.f.g...a...h.| +; CHECK-NEXT: 0100: 6F006800 6F006800 6F000000 24000200 |o.h.o.h.o...$...| +; CHECK-NEXT: 0110: 01004600 69006C00 65005600 65007200 |..F.i.l.e.V.e.r.| +; CHECK-NEXT: 0120: 73006900 6F006E00 00000000 63000000 |s.i.o.n.....c...| +; CHECK-NEXT: 0130: 24000200 01004900 6E007400 65007200 |$.....I.n.t.e.r.| +; CHECK-NEXT: 0140: 6E006100 6C004E00 61006D00 65000000 |n.a.l.N.a.m.e...| +; CHECK-NEXT: 0150: 64000000 2A000300 01004C00 65006700 |d...*.....L.e.g.| +; CHECK-NEXT: 0160: 61006C00 43006F00 70007900 72006900 |a.l.C.o.p.y.r.i.| +; CHECK-NEXT: 0170: 67006800 74000000 65003000 00000000 |g.h.t...e.0.....| +; CHECK-NEXT: 0180: 2E000600 00004C00 65006700 61006C00 |......L.e.g.a.l.| +; CHECK-NEXT: 0190: 54007200 61006400 65006D00 61007200 |T.r.a.d.e.m.a.r.| +; CHECK-NEXT: 01A0: 6B007300 31000000 01000200 03000000 |k.s.1...........| +; CHECK-NEXT: 01B0: 2C000200 01004C00 65006700 61006C00 |,.....L.e.g.a.l.| +; CHECK-NEXT: 01C0: 54007200 61006400 65006D00 61007200 |T.r.a.d.e.m.a.r.| +; CHECK-NEXT: 01D0: 6B007300 32000000 67000000 2C000200 |k.s.2...g...,...| +; CHECK-NEXT: 01E0: 01004F00 72006900 67006900 6E006100 |..O.r.i.g.i.n.a.| +; CHECK-NEXT: 01F0: 6C004600 69006C00 65006E00 61006D00 |l.F.i.l.e.n.a.m.| +; CHECK-NEXT: 0200: 65000000 68000000 2A000500 01005000 |e...h...*.....P.| +; CHECK-NEXT: 0210: 72006F00 64007500 63007400 4E006100 |r.o.d.u.c.t.N.a.| +; CHECK-NEXT: 0220: 6D006500 00000000 61006200 00006300 |m.e.....a.b...c.| +; CHECK-NEXT: 0230: 00000000 28000400 00005000 72006F00 |....(.....P.r.o.| +; CHECK-NEXT: 0240: 64007500 63007400 56006500 72007300 |d.u.c.t.V.e.r.s.| +; CHECK-NEXT: 0250: 69006F00 6E000000 78563412 44000000 |i.o.n...xV4.D...| +; CHECK-NEXT: 0260: 01005600 61007200 46006900 6C006500 |..V.a.r.F.i.l.e.| +; CHECK-NEXT: 0270: 49006E00 66006F00 00000000 24000400 |I.n.f.o.....$...| +; CHECK-NEXT: 0280: 00005400 72006100 6E007300 6C006100 |..T.r.a.n.s.l.a.| +; CHECK-NEXT: 0290: 74006900 6F006E00 00000000 0904E404 |t.i.o.n.........| +; CHECK-NEXT: ) + + +; RUN: not llvm-rc /FO %t %p/Inputs/tag-versioninfo-mixed-ints-strings.rc 2>&1 | FileCheck %s --check-prefix STRINT +; STRINT: llvm-rc: Error in VERSIONINFO statement (ID 1): +; STRINT-NEXT: VALUE "FileDescription" cannot contain both strings and integers + + +; RUN: not llvm-rc /FO %t %p/Inputs/tag-versioninfo-word-too-large.rc 2>&1 | FileCheck %s --check-prefix WORD +; WORD: llvm-rc: Error in VERSIONINFO statement (ID 1): +; WORD-NEXT: VERSIONINFO integer value (65536) does not fit in 16 bits. Index: llvm/tools/llvm-rc/ResourceFileWriter.h =================================================================== --- llvm/tools/llvm-rc/ResourceFileWriter.h +++ llvm/tools/llvm-rc/ResourceFileWriter.h @@ -36,6 +36,7 @@ Error visitHTMLResource(const RCResource *) override; Error visitIconResource(const RCResource *) override; Error visitMenuResource(const RCResource *) override; + Error visitVersionInfoResource(const RCResource *) override; Error visitCaptionStmt(const CaptionStmt *) override; Error visitCharacteristicsStmt(const CharacteristicsStmt *) override; @@ -98,6 +99,11 @@ Error writeMenuDefinitionList(const MenuDefinitionList &List); Error writeMenuBody(const RCResource *); + // VersionInfoResource + Error writeVersionInfoBody(const RCResource *); + Error writeVersionInfoBlock(const VersionInfoBlock &); + Error writeVersionInfoValue(const VersionInfoValue &); + // Output stream handling. std::unique_ptr FS; @@ -126,6 +132,8 @@ Error writeIdentifier(const IntOrString &Ident); Error writeIntOrString(const IntOrString &Data); + void writeRCInt(RCInt); + Error appendFile(StringRef Filename); void padStream(uint64_t Length); Index: llvm/tools/llvm-rc/ResourceFileWriter.cpp =================================================================== --- llvm/tools/llvm-rc/ResourceFileWriter.cpp +++ llvm/tools/llvm-rc/ResourceFileWriter.cpp @@ -28,7 +28,7 @@ namespace llvm { namespace rc { -// Class that employs RAII to save the current serializator object state +// Class that employs RAII to save the current FileWriter object state // and revert to it as soon as we leave the scope. This is useful if resources // declare their own resource-local statements. class ContextKeeper { @@ -79,6 +79,12 @@ return Error::success(); } +static Error checkRCInt(RCInt Number, Twine FieldName) { + if (Number.isLong()) + return Error::success(); + return checkNumberFits(Number, FieldName); +} + static Error checkIntOrString(IntOrString Value, Twine FieldName) { if (!Value.isInt()) return Error::success(); @@ -177,6 +183,13 @@ return Error::success(); } +void ResourceFileWriter::writeRCInt(RCInt Value) { + if (Value.isLong()) + writeObject((uint32_t)Value); + else + writeObject((uint16_t)Value); +} + Error ResourceFileWriter::appendFile(StringRef Filename) { bool IsLong; stripQuotes(Filename, IsLong); @@ -245,6 +258,10 @@ return writeResource(Res, &ResourceFileWriter::writeMenuBody); } +Error ResourceFileWriter::visitVersionInfoResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeVersionInfoBody); +} + Error ResourceFileWriter::visitCharacteristicsStmt( const CharacteristicsStmt *Stmt) { ObjectData.Characteristics = Stmt->Value; @@ -923,5 +940,178 @@ return writeMenuDefinitionList(cast(Base)->Elements); } +// --- VersionInfoResourceResource helpers. --- // + +Error ResourceFileWriter::writeVersionInfoBlock(const VersionInfoBlock &Blk) { + // Output the header if the block has name. + bool OutputHeader = Blk.Name != ""; + uint64_t LengthLoc; + + if (OutputHeader) { + LengthLoc = writeObject(0); + writeObject(0); + writeObject(true); + RETURN_IF_ERROR(writeCString(Blk.Name)); + padStream(sizeof(uint32_t)); + } + + for (const std::unique_ptr &Item : Blk.Stmts) { + VersionInfoStmt *ItemPtr = Item.get(); + + if (auto *BlockPtr = dyn_cast(ItemPtr)) { + RETURN_IF_ERROR(writeVersionInfoBlock(*BlockPtr)); + continue; + } + + auto *ValuePtr = cast(ItemPtr); + RETURN_IF_ERROR(writeVersionInfoValue(*ValuePtr)); + } + + if (OutputHeader) { + uint64_t CurLoc = tell(); + writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc); + } + + padStream(sizeof(uint32_t)); + return Error::success(); +} + +Error ResourceFileWriter::writeVersionInfoValue(const VersionInfoValue &Val) { + // rc has a peculiar algorithm to output VERSIONINFO VALUEs. Each VALUE + // is a mapping from the key (string) to the value (a sequence of ints or + // a sequence of strings). + // + // If integers are to be written: width of each integer written depends on + // whether it's been declared 'long' (it's DWORD then) or not (it's WORD). + // ValueLength defined in structure referenced below is then the total + // number of bytes taken by these integers. + // + // If strings are to be written: characters are always WORDs. + // Moreover, '\0' character is written after the last string, and between + // every two strings separated by comma (if strings are not comma-separated, + // they're simply concatenated). ValueLength is equal to the number of WORDs + // written (that is, half of the bytes written). + // + // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms646994.aspx + bool HasStrings = false, HasInts = false; + for (auto &Item : Val.Values) + (Item.isInt() ? HasInts : HasStrings) = true; + + assert((HasStrings || HasInts) && "VALUE must have at least one argument"); + if (HasStrings && HasInts) + return createError(Twine("VALUE ") + Val.Key + + " cannot contain both strings and integers"); + + auto LengthLoc = writeObject(0); + auto ValLengthLoc = writeObject(0); + writeObject(HasStrings); + RETURN_IF_ERROR(writeCString(Val.Key)); + padStream(sizeof(uint32_t)); + + auto DataLoc = tell(); + for (size_t Id = 0; Id < Val.Values.size(); ++Id) { + auto &Item = Val.Values[Id]; + if (Item.isInt()) { + auto Value = Item.getInt(); + RETURN_IF_ERROR(checkRCInt(Value, "VERSIONINFO integer value")); + writeRCInt(Value); + continue; + } + + bool WriteTerminator = + Id == Val.Values.size() - 1 || Val.HasPrecedingComma[Id + 1]; + RETURN_IF_ERROR(writeCString(Item.getString(), WriteTerminator)); + } + + auto CurLoc = tell(); + auto ValueLength = CurLoc - DataLoc; + if (HasStrings) { + assert(ValueLength % 2 == 0); + ValueLength /= 2; + } + writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc); + writeObjectAt(ulittle16_t(ValueLength), ValLengthLoc); + padStream(sizeof(uint32_t)); + return Error::success(); +} + +template +static Ty getWithDefault(const StringMap &Map, StringRef Key, + const Ty &Default) { + auto Iter = Map.find(Key); + if (Iter != Map.end()) + return Iter->getValue(); + return Default; +} + +Error ResourceFileWriter::writeVersionInfoBody(const RCResource *Base) { + auto *Res = cast(Base); + + const auto &FixedData = Res->FixedData; + + struct /* VS_FIXEDFILEINFO */ { + ulittle32_t Signature = ulittle32_t(0xFEEF04BD); + ulittle32_t StructVersion = ulittle32_t(0x10000); + // It's weird to have most-significant DWORD first on the little-endian + // machines, but let it be this way. + ulittle32_t FileVersionMS; + ulittle32_t FileVersionLS; + ulittle32_t ProductVersionMS; + ulittle32_t ProductVersionLS; + ulittle32_t FileFlagsMask; + ulittle32_t FileFlags; + ulittle32_t FileOS; + ulittle32_t FileType; + ulittle32_t FileSubtype; + // MS implementation seems to always set these fields to 0. + ulittle32_t FileDateMS = ulittle32_t(0); + ulittle32_t FileDateLS = ulittle32_t(0); + } FixedInfo; + + // First, VS_VERSIONINFO. + auto LengthLoc = writeObject(0); + writeObject(ulittle16_t(sizeof(FixedInfo))); + writeObject(ulittle16_t(0)); + cantFail(writeCString("VS_VERSION_INFO")); + padStream(sizeof(uint32_t)); + + using VersionInfoFixed = VersionInfoResource::VersionInfoFixed; + auto GetField = [&](VersionInfoFixed::VersionInfoFixedType Type) { + static const SmallVector DefaultOut{0, 0, 0, 0}; + if (!FixedData.IsTypePresent[(int)Type]) + return DefaultOut; + return FixedData.FixedInfo[(int)Type]; + }; + + auto FileVer = GetField(VersionInfoFixed::FtFileVersion); + RETURN_IF_ERROR(checkNumberFits( + *std::max_element(FileVer.begin(), FileVer.end()), "FILEVERSION fields")); + FixedInfo.FileVersionMS = (FileVer[0] << 16) | FileVer[1]; + FixedInfo.FileVersionLS = (FileVer[2] << 16) | FileVer[3]; + + auto ProdVer = GetField(VersionInfoFixed::FtProductVersion); + RETURN_IF_ERROR(checkNumberFits( + *std::max_element(ProdVer.begin(), ProdVer.end()), + "PRODUCTVERSION fields")); + FixedInfo.ProductVersionMS = (ProdVer[0] << 16) | ProdVer[1]; + FixedInfo.ProductVersionLS = (ProdVer[2] << 16) | ProdVer[3]; + + FixedInfo.FileFlagsMask = GetField(VersionInfoFixed::FtFileFlagsMask)[0]; + FixedInfo.FileFlags = GetField(VersionInfoFixed::FtFileFlags)[0]; + FixedInfo.FileOS = GetField(VersionInfoFixed::FtFileOS)[0]; + FixedInfo.FileType = GetField(VersionInfoFixed::FtFileType)[0]; + FixedInfo.FileSubtype = GetField(VersionInfoFixed::FtFileSubtype)[0]; + + writeObject(FixedInfo); + padStream(sizeof(uint32_t)); + + RETURN_IF_ERROR(writeVersionInfoBlock(Res->MainBlock)); + + // FIXME: check overflow? + writeObjectAt(ulittle16_t(tell() - LengthLoc), LengthLoc); + + return Error::success(); +} + } // namespace rc } // namespace llvm Index: llvm/tools/llvm-rc/ResourceScriptParser.h =================================================================== --- llvm/tools/llvm-rc/ResourceScriptParser.h +++ llvm/tools/llvm-rc/ResourceScriptParser.h @@ -79,15 +79,15 @@ // correct type and then parse it. // Each integer can be written as an arithmetic expression producing an // unsigned 32-bit integer. - Expected readInt(); // Parse an integer. + Expected readInt(); // Parse an integer. Expected readString(); // Parse a string. Expected readIdentifier(); // Parse an identifier. Expected readIntOrString(); // Parse an integer or a string. Expected readTypeOrName(); // Parse an integer or an identifier. // Helper integer expression parsing methods. - Expected parseIntExpr1(); - Expected parseIntExpr2(); + Expected parseIntExpr1(); + Expected parseIntExpr2(); // Advance the state by one, discarding the current token. // If the discarded token had an incorrect type, fail. @@ -101,8 +101,8 @@ // commas. The parser stops reading after fetching MaxCount integers // or after an error occurs. Whenever the parser reads a comma, it // expects an integer to follow. - Expected> readIntsWithCommas(size_t MinCount, - size_t MaxCount); + Expected> readIntsWithCommas(size_t MinCount, + size_t MaxCount); // Read an unknown number of flags preceded by commas. Each correct flag // has an entry in FlagDesc array of length NumFlags. In case i-th Index: llvm/tools/llvm-rc/ResourceScriptParser.cpp =================================================================== --- llvm/tools/llvm-rc/ResourceScriptParser.cpp +++ llvm/tools/llvm-rc/ResourceScriptParser.cpp @@ -134,12 +134,12 @@ // 1 => 01 00, -1 => ff ff, --1 => 01 00, ---1 => ff ff; // 1 => 01 00, ~1 => fe ff, ~~1 => 01 00, ~~~1 => fe ff. -Expected RCParser::readInt() { return parseIntExpr1(); } +Expected RCParser::readInt() { return parseIntExpr1(); } -Expected RCParser::parseIntExpr1() { +Expected RCParser::parseIntExpr1() { // Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2. ASSIGN_OR_RETURN(FirstResult, parseIntExpr2()); - uint32_t Result = *FirstResult; + RCInt Result = *FirstResult; while (!isEof() && look().isBinaryOp()) { auto OpToken = read(); @@ -170,7 +170,7 @@ return Result; } -Expected RCParser::parseIntExpr2() { +Expected RCParser::parseIntExpr2() { // Exp2 ::= -Exp2 || ~Exp2 || Int || (Exp1). static const char ErrorMsg[] = "'-', '~', integer or '('"; @@ -191,7 +191,7 @@ } case Kind::Int: - return read().intValue(); + return RCInt(read()); case Kind::LeftParen: { consume(); @@ -261,14 +261,14 @@ return false; } -Expected> -RCParser::readIntsWithCommas(size_t MinCount, size_t MaxCount) { +Expected> RCParser::readIntsWithCommas(size_t MinCount, + size_t MaxCount) { assert(MinCount <= MaxCount); - SmallVector Result; + SmallVector Result; auto FailureHandler = - [&](llvm::Error Err) -> Expected> { + [&](llvm::Error Err) -> Expected> { if (Result.size() < MinCount) return std::move(Err); consumeError(std::move(Err)); @@ -476,7 +476,7 @@ ASSIGN_OR_RETURN(Args, readIntsWithCommas(5, 8)); auto TakeOptArg = [&Args](size_t Id) -> Optional { - return Args->size() > Id ? (*Args)[Id] : Optional(); + return Args->size() > Id ? (uint32_t)(*Args)[Id] : Optional(); }; return Control(*ClassResult, Caption, (*Args)[0], (*Args)[1], (*Args)[2], @@ -606,15 +606,23 @@ 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. + // Read a non-empty list of strings and/or ints, each + // possibly preceded by a comma. Unfortunately, the tool behavior depends + // on them existing or not, so we need to memorize where we found them. std::vector Values; + std::vector PrecedingCommas; + RETURN_IF_ERROR(consumeType(Kind::Comma)); - while (consumeOptionalType(Kind::Comma)) { + while (!isNextTokenKind(Kind::Identifier) && + !isNextTokenKind(Kind::BlockEnd)) { + // Try to eat a comma if it's not the first statement. + bool HadComma = Values.size() > 0 && consumeOptionalType(Kind::Comma); ASSIGN_OR_RETURN(ValueResult, readIntOrString()); Values.push_back(*ValueResult); + PrecedingCommas.push_back(HadComma); } - return make_unique(*KeyResult, std::move(Values)); + return llvm::make_unique(*KeyResult, std::move(Values), + std::move(PrecedingCommas)); } return getExpectedError("BLOCK or VALUE", true); @@ -639,7 +647,8 @@ // VERSION variations take multiple integers. size_t NumInts = RetType::isVersionType(FixedType) ? 4 : 1; ASSIGN_OR_RETURN(ArgsResult, readIntsWithCommas(NumInts, NumInts)); - Result.setValue(FixedType, *ArgsResult); + SmallVector ArgInts(ArgsResult->begin(), ArgsResult->end()); + Result.setValue(FixedType, ArgInts); } return Result; Index: llvm/tools/llvm-rc/ResourceScriptStmt.h =================================================================== --- llvm/tools/llvm-rc/ResourceScriptStmt.h +++ llvm/tools/llvm-rc/ResourceScriptStmt.h @@ -22,17 +22,62 @@ namespace llvm { namespace rc { +// Integer wrapper that also holds information whether the user declared +// the integer to be long (by appending L to the end of the integer) or not. +// It allows to be implicitly cast from and to uint32_t in order +// to be compatible with the parts of code that don't care about the integers +// being marked long. +class RCInt { + uint32_t Val; + bool Long; + +public: + RCInt(const RCToken &Token) + : Val(Token.intValue()), Long(Token.isLongInt()) {} + RCInt(uint32_t Value) : Val(Value), Long(false) {} + RCInt(uint32_t Value, bool IsLong) : Val(Value), Long(IsLong) {} + operator uint32_t() const { return Val; } + bool isLong() const { return Long; } + + RCInt &operator+=(const RCInt &Rhs) { + std::tie(Val, Long) = std::make_pair(Val + Rhs.Val, Long | Rhs.Long); + return *this; + } + + RCInt &operator-=(const RCInt &Rhs) { + std::tie(Val, Long) = std::make_pair(Val - Rhs.Val, Long | Rhs.Long); + return *this; + } + + RCInt &operator|=(const RCInt &Rhs) { + std::tie(Val, Long) = std::make_pair(Val | Rhs.Val, Long | Rhs.Long); + return *this; + } + + RCInt &operator&=(const RCInt &Rhs) { + std::tie(Val, Long) = std::make_pair(Val & Rhs.Val, Long | Rhs.Long); + return *this; + } + + RCInt operator-() const { return {-Val, Long}; } + RCInt operator~() const { return {~Val, Long}; } + + friend raw_ostream &operator<<(raw_ostream &OS, const RCInt &Int) { + return OS << Int.Val << (Int.Long ? "L" : ""); + } +}; + // A class holding a name - either an integer or a reference to the string. class IntOrString { private: union Data { - uint32_t Int; + RCInt Int; StringRef String; - Data(uint32_t Value) : Int(Value) {} + Data(RCInt Value) : Int(Value) {} Data(const StringRef Value) : String(Value) {} Data(const RCToken &Token) { if (Token.kind() == RCToken::Kind::Int) - Int = Token.intValue(); + Int = RCInt(Token); else String = Token.value(); } @@ -40,8 +85,9 @@ bool IsInt; public: - IntOrString() : IntOrString(0) {} + IntOrString() : IntOrString(RCInt(0)) {} IntOrString(uint32_t Value) : Data(Value), IsInt(1) {} + IntOrString(RCInt Value) : Data(Value), IsInt(1) {} IntOrString(StringRef Value) : Data(Value), IsInt(0) {} IntOrString(const RCToken &Token) : Data(Token), IsInt(Token.kind() == RCToken::Kind::Int) {} @@ -52,7 +98,7 @@ bool isInt() const { return IsInt; } - uint32_t getInt() const { + RCInt getInt() const { assert(IsInt); return Data.Int; } @@ -573,8 +619,15 @@ // A single VERSIONINFO statement; class VersionInfoStmt { public: + enum StmtKind { StBase = 0, StBlock = 1, StValue = 2 }; + virtual raw_ostream &log(raw_ostream &OS) const { return OS << "VI stmt\n"; } virtual ~VersionInfoStmt() {} + + virtual StmtKind getKind() const { return StBase; } + static bool classof(const VersionInfoStmt *S) { + return S->getKind() == StBase; + } }; // BLOCK definition; also the main VERSIONINFO declaration is considered a @@ -582,25 +635,38 @@ // The correct top-level blocks are "VarFileInfo" and "StringFileInfo". We don't // care about them at the parsing phase. class VersionInfoBlock : public VersionInfoStmt { +public: 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; + + StmtKind getKind() const override { return StBlock; } + static bool classof(const VersionInfoStmt *S) { + return S->getKind() == StBlock; + } }; class VersionInfoValue : public VersionInfoStmt { +public: StringRef Key; std::vector Values; + std::vector HasPrecedingComma; -public: - VersionInfoValue(StringRef InfoKey, std::vector &&Vals) - : Key(InfoKey), Values(std::move(Vals)) {} + VersionInfoValue(StringRef InfoKey, std::vector &&Vals, + std::vector &&CommasBeforeVals) + : Key(InfoKey), Values(std::move(Vals)), + HasPrecedingComma(std::move(CommasBeforeVals)) {} raw_ostream &log(raw_ostream &) const override; + + StmtKind getKind() const override { return StValue; } + static bool classof(const VersionInfoStmt *S) { + return S->getKind() == StValue; + } }; class VersionInfoResource : public RCResource { @@ -644,16 +710,24 @@ 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; + IntOrString getResourceType() const override { return RkVersionInfo; } + uint16_t getMemoryFlags() const override { return MfMoveable | MfPure; } + Twine getResourceTypeName() const override { return "VERSIONINFO"; } + Error visit(Visitor *V) const override { + return V->visitVersionInfoResource(this); + } + ResourceKind getKind() const override { return RkVersionInfo; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkVersionInfo; + } }; // CHARACTERISTICS optional statement. Index: llvm/tools/llvm-rc/ResourceScriptStmt.cpp =================================================================== --- llvm/tools/llvm-rc/ResourceScriptStmt.cpp +++ llvm/tools/llvm-rc/ResourceScriptStmt.cpp @@ -161,8 +161,12 @@ raw_ostream &VersionInfoValue::log(raw_ostream &OS) const { OS << " " << Key << " =>"; - for (auto &Value : Values) - OS << " " << Value; + size_t NumValues = Values.size(); + for (size_t Id = 0; Id < NumValues; ++Id) { + if (Id > 0 && HasPrecedingComma[Id]) + OS << ","; + OS << " " << Values[Id]; + } return OS << "\n"; } Index: llvm/tools/llvm-rc/ResourceScriptToken.h =================================================================== --- llvm/tools/llvm-rc/ResourceScriptToken.h +++ llvm/tools/llvm-rc/ResourceScriptToken.h @@ -56,6 +56,7 @@ // Get an integer value of the integer token. uint32_t intValue() const; + bool isLongInt() const; StringRef value() const; Kind kind() const; Index: llvm/tools/llvm-rc/ResourceScriptToken.cpp =================================================================== --- llvm/tools/llvm-rc/ResourceScriptToken.cpp +++ llvm/tools/llvm-rc/ResourceScriptToken.cpp @@ -56,6 +56,10 @@ return Result; } +bool RCToken::isLongInt() const { + return TokenKind == Kind::Int && std::toupper(TokenValue.back()) == 'L'; +} + StringRef RCToken::value() const { return TokenValue; } Kind RCToken::kind() const { return TokenKind; } Index: llvm/tools/llvm-rc/ResourceVisitor.h =================================================================== --- llvm/tools/llvm-rc/ResourceVisitor.h +++ llvm/tools/llvm-rc/ResourceVisitor.h @@ -37,6 +37,7 @@ virtual Error visitHTMLResource(const RCResource *) = 0; virtual Error visitIconResource(const RCResource *) = 0; virtual Error visitMenuResource(const RCResource *) = 0; + virtual Error visitVersionInfoResource(const RCResource *) = 0; virtual Error visitCaptionStmt(const CaptionStmt *) = 0; virtual Error visitCharacteristicsStmt(const CharacteristicsStmt *) = 0;