diff options
author | vitalyisaev <vitalyisaev@yandex-team.com> | 2023-06-29 10:00:50 +0300 |
---|---|---|
committer | vitalyisaev <vitalyisaev@yandex-team.com> | 2023-06-29 10:00:50 +0300 |
commit | 6ffe9e53658409f212834330e13564e4952558f6 (patch) | |
tree | 85b1e00183517648b228aafa7c8fb07f5276f419 /contrib/libs/llvm14/tools/llvm-rc | |
parent | 726057070f9c5a91fc10fde0d5024913d10f1ab9 (diff) | |
download | ydb-6ffe9e53658409f212834330e13564e4952558f6.tar.gz |
YQ Connector: support managed ClickHouse
Со стороны dqrun можно обратиться к инстансу коннектора, который работает на streaming стенде, и извлечь данные из облачного CH.
Diffstat (limited to 'contrib/libs/llvm14/tools/llvm-rc')
16 files changed, 5696 insertions, 0 deletions
diff --git a/contrib/libs/llvm14/tools/llvm-rc/Opts.td b/contrib/libs/llvm14/tools/llvm-rc/Opts.td new file mode 100644 index 0000000000..6d9c0e2601 --- /dev/null +++ b/contrib/libs/llvm14/tools/llvm-rc/Opts.td @@ -0,0 +1,65 @@ +include "llvm/Option/OptParser.td" + +// All the switches can be preceded by either '/' or '-'. +// These options seem to be important for the tool +// and should be implemented. + +class S<string name, string help> : + Separate<["/", "-"], name>, HelpText<help>; + +class JS<string name, string help> : + JoinedOrSeparate<["/", "-"], name>, HelpText<help>; + +class F<string name, string help> : Flag<["/", "-"], name>, HelpText<help>; + +class F_nodoc<string name> : Flag<["/", "-"], name>; +class S_nodoc<string name> : Separate<["/", "-"], name>; + +def fileout : JS<"FO", "Change the output file location.">; + +def define : JS<"D", "Define a symbol for the C preprocessor.">; +def undef : JS<"U", "Undefine a symbol for the C preprocessor.">; + +def lang_id : JS<"L", "Set the default language identifier.">; +def lang_name : S<"LN", "Set the default language name.">; + +def includepath : JS<"I", "Add an include path.">; +def noinclude : F<"X", "Ignore 'include' variable.">; + +def add_null : F<"N", "Null-terminate all strings in the string table.">; + +def dupid_nowarn : F<"Y", "Suppress warnings on duplicate resource IDs.">; + +def verbose : F<"V", "Be verbose.">; +def help : F<"?", "Display this help and exit.">; +def h : F<"H", "Display this help and exit.">, Alias<help>; + +def codepage : JS<"C", "Set the codepage used for input strings.">; + +// llvm-rc specific options: + +def dry_run : F<"dry-run", "Don't compile the input; only try to parse it.">; + +def no_preprocess : F<"no-preprocess", "Don't try to preprocess the input file.">; + +// Print (but do not run) the commands to run for preprocessing +def _HASH_HASH_HASH : F_nodoc<"###">; + +// Unused switches (at least for now). These will stay unimplemented +// in an early stage of development and can be ignored. However, we need to +// parse them in order to preserve the compatibility with the original tool. + +def nologo : F_nodoc<"NOLOGO">; +def r : F_nodoc<"R">; +def sl : F_nodoc<"SL">; + +// (Codepages support.) +def w : F_nodoc<"W">; + +// (Support of MUI and similar.) +def fm : S_nodoc<"FM">; +def q : S_nodoc<"Q">; +def g : F_nodoc<"G">; +def gn : F_nodoc<"GN">; +def g1 : F_nodoc<"G1">; +def g2 : F_nodoc<"G2">; diff --git a/contrib/libs/llvm14/tools/llvm-rc/ResourceFileWriter.cpp b/contrib/libs/llvm14/tools/llvm-rc/ResourceFileWriter.cpp new file mode 100644 index 0000000000..60287a37f0 --- /dev/null +++ b/contrib/libs/llvm14/tools/llvm-rc/ResourceFileWriter.cpp @@ -0,0 +1,1567 @@ +//===-- ResourceFileWriter.cpp --------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This implements the visitor serializing resources to a .res stream. +// +//===---------------------------------------------------------------------===// + +#include "ResourceFileWriter.h" +#include "llvm/Object/WindowsResource.h" +#include "llvm/Support/ConvertUTF.h" +#include "llvm/Support/Endian.h" +#include "llvm/Support/EndianStream.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Process.h" + +using namespace llvm::support; + +// Take an expression returning llvm::Error and forward the error if it exists. +#define RETURN_IF_ERROR(Expr) \ + if (auto Err = (Expr)) \ + return Err; + +namespace llvm { +namespace rc { + +// 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 { + ResourceFileWriter *FileWriter; + ResourceFileWriter::ObjectInfo SavedInfo; + +public: + ContextKeeper(ResourceFileWriter *V) + : FileWriter(V), SavedInfo(V->ObjectData) {} + ~ContextKeeper() { FileWriter->ObjectData = SavedInfo; } +}; + +static Error createError(const Twine &Message, + std::errc Type = std::errc::invalid_argument) { + return make_error<StringError>(Message, std::make_error_code(Type)); +} + +static Error checkNumberFits(uint32_t Number, size_t MaxBits, + const Twine &FieldName) { + assert(1 <= MaxBits && MaxBits <= 32); + if (!(Number >> MaxBits)) + return Error::success(); + return createError(FieldName + " (" + Twine(Number) + ") does not fit in " + + Twine(MaxBits) + " bits.", + std::errc::value_too_large); +} + +template <typename FitType> +static Error checkNumberFits(uint32_t Number, const Twine &FieldName) { + return checkNumberFits(Number, sizeof(FitType) * 8, FieldName); +} + +// A similar function for signed integers. +template <typename FitType> +static Error checkSignedNumberFits(uint32_t Number, const Twine &FieldName, + bool CanBeNegative) { + int32_t SignedNum = Number; + if (SignedNum < std::numeric_limits<FitType>::min() || + SignedNum > std::numeric_limits<FitType>::max()) + return createError(FieldName + " (" + Twine(SignedNum) + + ") does not fit in " + Twine(sizeof(FitType) * 8) + + "-bit signed integer type.", + std::errc::value_too_large); + + if (!CanBeNegative && SignedNum < 0) + return createError(FieldName + " (" + Twine(SignedNum) + + ") cannot be negative."); + + return Error::success(); +} + +static Error checkRCInt(RCInt Number, const Twine &FieldName) { + if (Number.isLong()) + return Error::success(); + return checkNumberFits<uint16_t>(Number, FieldName); +} + +static Error checkIntOrString(IntOrString Value, const Twine &FieldName) { + if (!Value.isInt()) + return Error::success(); + return checkNumberFits<uint16_t>(Value.getInt(), FieldName); +} + +static bool stripQuotes(StringRef &Str, bool &IsLongString) { + if (!Str.contains('"')) + return false; + + // Just take the contents of the string, checking if it's been marked long. + IsLongString = Str.startswith_insensitive("L"); + if (IsLongString) + Str = Str.drop_front(); + + bool StripSuccess = Str.consume_front("\"") && Str.consume_back("\""); + (void)StripSuccess; + assert(StripSuccess && "Strings should be enclosed in quotes."); + return true; +} + +static UTF16 cp1252ToUnicode(unsigned char C) { + static const UTF16 Map80[] = { + 0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f, + 0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178, + }; + if (C >= 0x80 && C <= 0x9F) + return Map80[C - 0x80]; + return C; +} + +// Describes a way to handle '\0' characters when processing the string. +// rc.exe tool sometimes behaves in a weird way in postprocessing. +// If the string to be output is equivalent to a C-string (e.g. in MENU +// titles), string is (predictably) truncated after first 0-byte. +// When outputting a string table, the behavior is equivalent to appending +// '\0\0' at the end of the string, and then stripping the string +// before the first '\0\0' occurrence. +// Finally, when handling strings in user-defined resources, 0-bytes +// aren't stripped, nor do they terminate the string. + +enum class NullHandlingMethod { + UserResource, // Don't terminate string on '\0'. + CutAtNull, // Terminate string on '\0'. + CutAtDoubleNull // Terminate string on '\0\0'; strip final '\0'. +}; + +// Parses an identifier or string and returns a processed version of it: +// * Strip the string boundary quotes. +// * Convert the input code page characters to UTF16. +// * Squash "" to a single ". +// * Replace the escape sequences with their processed version. +// For identifiers, this is no-op. +static Error processString(StringRef Str, NullHandlingMethod NullHandler, + bool &IsLongString, SmallVectorImpl<UTF16> &Result, + int CodePage) { + bool IsString = stripQuotes(Str, IsLongString); + SmallVector<UTF16, 128> Chars; + + // Convert the input bytes according to the chosen codepage. + if (CodePage == CpUtf8) { + convertUTF8ToUTF16String(Str, Chars); + } else if (CodePage == CpWin1252) { + for (char C : Str) + Chars.push_back(cp1252ToUnicode((unsigned char)C)); + } else { + // For other, unknown codepages, only allow plain ASCII input. + for (char C : Str) { + if ((unsigned char)C > 0x7F) + return createError("Non-ASCII 8-bit codepoint (" + Twine(C) + + ") can't be interpreted in the current codepage"); + Chars.push_back((unsigned char)C); + } + } + + if (!IsString) { + // It's an identifier if it's not a string. Make all characters uppercase. + for (UTF16 &Ch : Chars) { + assert(Ch <= 0x7F && "We didn't allow identifiers to be non-ASCII"); + Ch = toupper(Ch); + } + Result.swap(Chars); + return Error::success(); + } + Result.reserve(Chars.size()); + size_t Pos = 0; + + auto AddRes = [&Result, NullHandler, IsLongString](UTF16 Char) -> Error { + if (!IsLongString) { + if (NullHandler == NullHandlingMethod::UserResource) { + // Narrow strings in user-defined resources are *not* output in + // UTF-16 format. + if (Char > 0xFF) + return createError("Non-8-bit codepoint (" + Twine(Char) + + ") can't occur in a user-defined narrow string"); + } + } + + Result.push_back(Char); + return Error::success(); + }; + auto AddEscapedChar = [AddRes, IsLongString, CodePage](UTF16 Char) -> Error { + if (!IsLongString) { + // Escaped chars in narrow strings have to be interpreted according to + // the chosen code page. + if (Char > 0xFF) + return createError("Non-8-bit escaped char (" + Twine(Char) + + ") can't occur in narrow string"); + if (CodePage == CpUtf8) { + if (Char >= 0x80) + return createError("Unable to interpret single byte (" + Twine(Char) + + ") as UTF-8"); + } else if (CodePage == CpWin1252) { + Char = cp1252ToUnicode(Char); + } else { + // Unknown/unsupported codepage, only allow ASCII input. + if (Char > 0x7F) + return createError("Non-ASCII 8-bit codepoint (" + Twine(Char) + + ") can't " + "occur in a non-Unicode string"); + } + } + + return AddRes(Char); + }; + + while (Pos < Chars.size()) { + UTF16 CurChar = Chars[Pos]; + ++Pos; + + // Strip double "". + if (CurChar == '"') { + if (Pos == Chars.size() || Chars[Pos] != '"') + return createError("Expected \"\""); + ++Pos; + RETURN_IF_ERROR(AddRes('"')); + continue; + } + + if (CurChar == '\\') { + UTF16 TypeChar = Chars[Pos]; + ++Pos; + + if (TypeChar == 'x' || TypeChar == 'X') { + // Read a hex number. Max number of characters to read differs between + // narrow and wide strings. + UTF16 ReadInt = 0; + size_t RemainingChars = IsLongString ? 4 : 2; + // We don't want to read non-ASCII hex digits. std:: functions past + // 0xFF invoke UB. + // + // FIXME: actually, Microsoft version probably doesn't check this + // condition and uses their Unicode version of 'isxdigit'. However, + // there are some hex-digit Unicode character outside of ASCII, and + // some of these are actually accepted by rc.exe, the notable example + // being fullwidth forms (U+FF10..U+FF19 etc.) These can be written + // instead of ASCII digits in \x... escape sequence and get accepted. + // However, the resulting hexcodes seem totally unpredictable. + // We think it's infeasible to try to reproduce this behavior, nor to + // put effort in order to detect it. + while (RemainingChars && Pos < Chars.size() && Chars[Pos] < 0x80) { + if (!isxdigit(Chars[Pos])) + break; + char Digit = tolower(Chars[Pos]); + ++Pos; + + ReadInt <<= 4; + if (isdigit(Digit)) + ReadInt |= Digit - '0'; + else + ReadInt |= Digit - 'a' + 10; + + --RemainingChars; + } + + RETURN_IF_ERROR(AddEscapedChar(ReadInt)); + continue; + } + + if (TypeChar >= '0' && TypeChar < '8') { + // Read an octal number. Note that we've already read the first digit. + UTF16 ReadInt = TypeChar - '0'; + size_t RemainingChars = IsLongString ? 6 : 2; + + while (RemainingChars && Pos < Chars.size() && Chars[Pos] >= '0' && + Chars[Pos] < '8') { + ReadInt <<= 3; + ReadInt |= Chars[Pos] - '0'; + --RemainingChars; + ++Pos; + } + + RETURN_IF_ERROR(AddEscapedChar(ReadInt)); + + continue; + } + + switch (TypeChar) { + case 'A': + case 'a': + // Windows '\a' translates into '\b' (Backspace). + RETURN_IF_ERROR(AddRes('\b')); + break; + + case 'n': // Somehow, RC doesn't recognize '\N' and '\R'. + RETURN_IF_ERROR(AddRes('\n')); + break; + + case 'r': + RETURN_IF_ERROR(AddRes('\r')); + break; + + case 'T': + case 't': + RETURN_IF_ERROR(AddRes('\t')); + break; + + case '\\': + RETURN_IF_ERROR(AddRes('\\')); + break; + + case '"': + // RC accepts \" only if another " comes afterwards; then, \"" means + // a single ". + if (Pos == Chars.size() || Chars[Pos] != '"') + return createError("Expected \\\"\""); + ++Pos; + RETURN_IF_ERROR(AddRes('"')); + break; + + default: + // If TypeChar means nothing, \ is should be output to stdout with + // following char. However, rc.exe consumes these characters when + // dealing with wide strings. + if (!IsLongString) { + RETURN_IF_ERROR(AddRes('\\')); + RETURN_IF_ERROR(AddRes(TypeChar)); + } + break; + } + + continue; + } + + // If nothing interesting happens, just output the character. + RETURN_IF_ERROR(AddRes(CurChar)); + } + + switch (NullHandler) { + case NullHandlingMethod::CutAtNull: + for (size_t Pos = 0; Pos < Result.size(); ++Pos) + if (Result[Pos] == '\0') + Result.resize(Pos); + break; + + case NullHandlingMethod::CutAtDoubleNull: + for (size_t Pos = 0; Pos + 1 < Result.size(); ++Pos) + if (Result[Pos] == '\0' && Result[Pos + 1] == '\0') + Result.resize(Pos); + if (Result.size() > 0 && Result.back() == '\0') + Result.pop_back(); + break; + + case NullHandlingMethod::UserResource: + break; + } + + return Error::success(); +} + +uint64_t ResourceFileWriter::writeObject(const ArrayRef<uint8_t> Data) { + uint64_t Result = tell(); + FS->write((const char *)Data.begin(), Data.size()); + return Result; +} + +Error ResourceFileWriter::writeCString(StringRef Str, bool WriteTerminator) { + SmallVector<UTF16, 128> ProcessedString; + bool IsLongString; + RETURN_IF_ERROR(processString(Str, NullHandlingMethod::CutAtNull, + IsLongString, ProcessedString, + Params.CodePage)); + for (auto Ch : ProcessedString) + writeInt<uint16_t>(Ch); + if (WriteTerminator) + writeInt<uint16_t>(0); + return Error::success(); +} + +Error ResourceFileWriter::writeIdentifier(const IntOrString &Ident) { + return writeIntOrString(Ident); +} + +Error ResourceFileWriter::writeIntOrString(const IntOrString &Value) { + if (!Value.isInt()) + return writeCString(Value.getString()); + + writeInt<uint16_t>(0xFFFF); + writeInt<uint16_t>(Value.getInt()); + return Error::success(); +} + +void ResourceFileWriter::writeRCInt(RCInt Value) { + if (Value.isLong()) + writeInt<uint32_t>(Value); + else + writeInt<uint16_t>(Value); +} + +Error ResourceFileWriter::appendFile(StringRef Filename) { + bool IsLong; + stripQuotes(Filename, IsLong); + + auto File = loadFile(Filename); + if (!File) + return File.takeError(); + + *FS << (*File)->getBuffer(); + return Error::success(); +} + +void ResourceFileWriter::padStream(uint64_t Length) { + assert(Length > 0); + uint64_t Location = tell(); + Location %= Length; + uint64_t Pad = (Length - Location) % Length; + for (uint64_t i = 0; i < Pad; ++i) + writeInt<uint8_t>(0); +} + +Error ResourceFileWriter::handleError(Error Err, const RCResource *Res) { + if (Err) + return joinErrors(createError("Error in " + Res->getResourceTypeName() + + " statement (ID " + Twine(Res->ResName) + + "): "), + std::move(Err)); + return Error::success(); +} + +Error ResourceFileWriter::visitNullResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeNullBody); +} + +Error ResourceFileWriter::visitAcceleratorsResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeAcceleratorsBody); +} + +Error ResourceFileWriter::visitBitmapResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeBitmapBody); +} + +Error ResourceFileWriter::visitCursorResource(const RCResource *Res) { + return handleError(visitIconOrCursorResource(Res), Res); +} + +Error ResourceFileWriter::visitDialogResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeDialogBody); +} + +Error ResourceFileWriter::visitIconResource(const RCResource *Res) { + return handleError(visitIconOrCursorResource(Res), Res); +} + +Error ResourceFileWriter::visitCaptionStmt(const CaptionStmt *Stmt) { + ObjectData.Caption = Stmt->Value; + return Error::success(); +} + +Error ResourceFileWriter::visitClassStmt(const ClassStmt *Stmt) { + ObjectData.Class = Stmt->Value; + return Error::success(); +} + +Error ResourceFileWriter::visitHTMLResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeHTMLBody); +} + +Error ResourceFileWriter::visitMenuResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeMenuBody); +} + +Error ResourceFileWriter::visitStringTableResource(const RCResource *Base) { + const auto *Res = cast<StringTableResource>(Base); + + ContextKeeper RAII(this); + RETURN_IF_ERROR(Res->applyStmts(this)); + + for (auto &String : Res->Table) { + RETURN_IF_ERROR(checkNumberFits<uint16_t>(String.first, "String ID")); + uint16_t BundleID = String.first >> 4; + StringTableInfo::BundleKey Key(BundleID, ObjectData.LanguageInfo); + auto &BundleData = StringTableData.BundleData; + auto Iter = BundleData.find(Key); + + if (Iter == BundleData.end()) { + // Need to create a bundle. + StringTableData.BundleList.push_back(Key); + auto EmplaceResult = BundleData.emplace( + Key, StringTableInfo::Bundle(ObjectData, Res->MemoryFlags)); + assert(EmplaceResult.second && "Could not create a bundle"); + Iter = EmplaceResult.first; + } + + RETURN_IF_ERROR( + insertStringIntoBundle(Iter->second, String.first, String.second)); + } + + return Error::success(); +} + +Error ResourceFileWriter::visitUserDefinedResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeUserDefinedBody); +} + +Error ResourceFileWriter::visitVersionInfoResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeVersionInfoBody); +} + +Error ResourceFileWriter::visitCharacteristicsStmt( + const CharacteristicsStmt *Stmt) { + ObjectData.Characteristics = Stmt->Value; + return Error::success(); +} + +Error ResourceFileWriter::visitExStyleStmt(const ExStyleStmt *Stmt) { + ObjectData.ExStyle = Stmt->Value; + return Error::success(); +} + +Error ResourceFileWriter::visitFontStmt(const FontStmt *Stmt) { + RETURN_IF_ERROR(checkNumberFits<uint16_t>(Stmt->Size, "Font size")); + RETURN_IF_ERROR(checkNumberFits<uint16_t>(Stmt->Weight, "Font weight")); + RETURN_IF_ERROR(checkNumberFits<uint8_t>(Stmt->Charset, "Font charset")); + ObjectInfo::FontInfo Font{Stmt->Size, Stmt->Name, Stmt->Weight, Stmt->Italic, + Stmt->Charset}; + ObjectData.Font.emplace(Font); + return Error::success(); +} + +Error ResourceFileWriter::visitLanguageStmt(const LanguageResource *Stmt) { + RETURN_IF_ERROR(checkNumberFits(Stmt->Lang, 10, "Primary language ID")); + RETURN_IF_ERROR(checkNumberFits(Stmt->SubLang, 6, "Sublanguage ID")); + ObjectData.LanguageInfo = Stmt->Lang | (Stmt->SubLang << 10); + return Error::success(); +} + +Error ResourceFileWriter::visitStyleStmt(const StyleStmt *Stmt) { + ObjectData.Style = Stmt->Value; + return Error::success(); +} + +Error ResourceFileWriter::visitVersionStmt(const VersionStmt *Stmt) { + ObjectData.VersionInfo = Stmt->Value; + return Error::success(); +} + +Error ResourceFileWriter::writeResource( + const RCResource *Res, + Error (ResourceFileWriter::*BodyWriter)(const RCResource *)) { + // We don't know the sizes yet. + object::WinResHeaderPrefix HeaderPrefix{ulittle32_t(0U), ulittle32_t(0U)}; + uint64_t HeaderLoc = writeObject(HeaderPrefix); + + auto ResType = Res->getResourceType(); + RETURN_IF_ERROR(checkIntOrString(ResType, "Resource type")); + RETURN_IF_ERROR(checkIntOrString(Res->ResName, "Resource ID")); + RETURN_IF_ERROR(handleError(writeIdentifier(ResType), Res)); + RETURN_IF_ERROR(handleError(writeIdentifier(Res->ResName), Res)); + + // Apply the resource-local optional statements. + ContextKeeper RAII(this); + RETURN_IF_ERROR(handleError(Res->applyStmts(this), Res)); + + padStream(sizeof(uint32_t)); + object::WinResHeaderSuffix HeaderSuffix{ + ulittle32_t(0), // DataVersion; seems to always be 0 + ulittle16_t(Res->MemoryFlags), ulittle16_t(ObjectData.LanguageInfo), + ulittle32_t(ObjectData.VersionInfo), + ulittle32_t(ObjectData.Characteristics)}; + writeObject(HeaderSuffix); + + uint64_t DataLoc = tell(); + RETURN_IF_ERROR(handleError((this->*BodyWriter)(Res), Res)); + // RETURN_IF_ERROR(handleError(dumpResource(Ctx))); + + // Update the sizes. + HeaderPrefix.DataSize = tell() - DataLoc; + HeaderPrefix.HeaderSize = DataLoc - HeaderLoc; + writeObjectAt(HeaderPrefix, HeaderLoc); + padStream(sizeof(uint32_t)); + + return Error::success(); +} + +// --- NullResource helpers. --- // + +Error ResourceFileWriter::writeNullBody(const RCResource *) { + return Error::success(); +} + +// --- AcceleratorsResource helpers. --- // + +Error ResourceFileWriter::writeSingleAccelerator( + const AcceleratorsResource::Accelerator &Obj, bool IsLastItem) { + using Accelerator = AcceleratorsResource::Accelerator; + using Opt = Accelerator::Options; + + struct AccelTableEntry { + ulittle16_t Flags; + ulittle16_t ANSICode; + ulittle16_t Id; + uint16_t Padding; + } Entry{ulittle16_t(0), ulittle16_t(0), ulittle16_t(0), 0}; + + bool IsASCII = Obj.Flags & Opt::ASCII, IsVirtKey = Obj.Flags & Opt::VIRTKEY; + + // Remove ASCII flags (which doesn't occur in .res files). + Entry.Flags = Obj.Flags & ~Opt::ASCII; + + if (IsLastItem) + Entry.Flags |= 0x80; + + RETURN_IF_ERROR(checkNumberFits<uint16_t>(Obj.Id, "ACCELERATORS entry ID")); + Entry.Id = ulittle16_t(Obj.Id); + + auto createAccError = [&Obj](const char *Msg) { + return createError("Accelerator ID " + Twine(Obj.Id) + ": " + Msg); + }; + + if (IsASCII && IsVirtKey) + return createAccError("Accelerator can't be both ASCII and VIRTKEY"); + + if (!IsVirtKey && (Obj.Flags & (Opt::ALT | Opt::SHIFT | Opt::CONTROL))) + return createAccError("Can only apply ALT, SHIFT or CONTROL to VIRTKEY" + " accelerators"); + + if (Obj.Event.isInt()) { + if (!IsASCII && !IsVirtKey) + return createAccError( + "Accelerator with a numeric event must be either ASCII" + " or VIRTKEY"); + + uint32_t EventVal = Obj.Event.getInt(); + RETURN_IF_ERROR( + checkNumberFits<uint16_t>(EventVal, "Numeric event key ID")); + Entry.ANSICode = ulittle16_t(EventVal); + writeObject(Entry); + return Error::success(); + } + + StringRef Str = Obj.Event.getString(); + bool IsWide; + stripQuotes(Str, IsWide); + + if (Str.size() == 0 || Str.size() > 2) + return createAccError( + "Accelerator string events should have length 1 or 2"); + + if (Str[0] == '^') { + if (Str.size() == 1) + return createAccError("No character following '^' in accelerator event"); + if (IsVirtKey) + return createAccError( + "VIRTKEY accelerator events can't be preceded by '^'"); + + char Ch = Str[1]; + if (Ch >= 'a' && Ch <= 'z') + Entry.ANSICode = ulittle16_t(Ch - 'a' + 1); + else if (Ch >= 'A' && Ch <= 'Z') + Entry.ANSICode = ulittle16_t(Ch - 'A' + 1); + else + return createAccError("Control character accelerator event should be" + " alphabetic"); + + writeObject(Entry); + return Error::success(); + } + + if (Str.size() == 2) + return createAccError("Event string should be one-character, possibly" + " preceded by '^'"); + + uint8_t EventCh = Str[0]; + // The original tool just warns in this situation. We chose to fail. + if (IsVirtKey && !isalnum(EventCh)) + return createAccError("Non-alphanumeric characters cannot describe virtual" + " keys"); + if (EventCh > 0x7F) + return createAccError("Non-ASCII description of accelerator"); + + if (IsVirtKey) + EventCh = toupper(EventCh); + Entry.ANSICode = ulittle16_t(EventCh); + writeObject(Entry); + return Error::success(); +} + +Error ResourceFileWriter::writeAcceleratorsBody(const RCResource *Base) { + auto *Res = cast<AcceleratorsResource>(Base); + size_t AcceleratorId = 0; + for (auto &Acc : Res->Accelerators) { + ++AcceleratorId; + RETURN_IF_ERROR( + writeSingleAccelerator(Acc, AcceleratorId == Res->Accelerators.size())); + } + return Error::success(); +} + +// --- BitmapResource helpers. --- // + +Error ResourceFileWriter::writeBitmapBody(const RCResource *Base) { + StringRef Filename = cast<BitmapResource>(Base)->BitmapLoc; + bool IsLong; + stripQuotes(Filename, IsLong); + + auto File = loadFile(Filename); + if (!File) + return File.takeError(); + + StringRef Buffer = (*File)->getBuffer(); + + // Skip the 14 byte BITMAPFILEHEADER. + constexpr size_t BITMAPFILEHEADER_size = 14; + if (Buffer.size() < BITMAPFILEHEADER_size || Buffer[0] != 'B' || + Buffer[1] != 'M') + return createError("Incorrect bitmap file."); + + *FS << Buffer.substr(BITMAPFILEHEADER_size); + return Error::success(); +} + +// --- CursorResource and IconResource helpers. --- // + +// ICONRESDIR structure. Describes a single icon in resource group. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648016.aspx +struct IconResDir { + uint8_t Width; + uint8_t Height; + uint8_t ColorCount; + uint8_t Reserved; +}; + +// CURSORDIR structure. Describes a single cursor in resource group. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648011(v=vs.85).aspx +struct CursorDir { + ulittle16_t Width; + ulittle16_t Height; +}; + +// RESDIRENTRY structure, stripped from the last item. Stripping made +// for compatibility with RESDIR. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026(v=vs.85).aspx +struct ResourceDirEntryStart { + union { + CursorDir Cursor; // Used in CURSOR resources. + IconResDir Icon; // Used in .ico and .cur files, and ICON resources. + }; + ulittle16_t Planes; // HotspotX (.cur files but not CURSOR resource). + ulittle16_t BitCount; // HotspotY (.cur files but not CURSOR resource). + ulittle32_t Size; + // ulittle32_t ImageOffset; // Offset to image data (ICONDIRENTRY only). + // ulittle16_t IconID; // Resource icon ID (RESDIR only). +}; + +// BITMAPINFOHEADER structure. Describes basic information about the bitmap +// being read. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx +struct BitmapInfoHeader { + ulittle32_t Size; + ulittle32_t Width; + ulittle32_t Height; + ulittle16_t Planes; + ulittle16_t BitCount; + ulittle32_t Compression; + ulittle32_t SizeImage; + ulittle32_t XPelsPerMeter; + ulittle32_t YPelsPerMeter; + ulittle32_t ClrUsed; + ulittle32_t ClrImportant; +}; + +// Group icon directory header. Called ICONDIR in .ico/.cur files and +// NEWHEADER in .res files. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648023(v=vs.85).aspx +struct GroupIconDir { + ulittle16_t Reserved; // Always 0. + ulittle16_t ResType; // 1 for icons, 2 for cursors. + ulittle16_t ResCount; // Number of items. +}; + +enum class IconCursorGroupType { Icon, Cursor }; + +class SingleIconCursorResource : public RCResource { +public: + IconCursorGroupType Type; + const ResourceDirEntryStart &Header; + ArrayRef<uint8_t> Image; + + SingleIconCursorResource(IconCursorGroupType ResourceType, + const ResourceDirEntryStart &HeaderEntry, + ArrayRef<uint8_t> ImageData, uint16_t Flags) + : RCResource(Flags), Type(ResourceType), Header(HeaderEntry), + Image(ImageData) {} + + Twine getResourceTypeName() const override { return "Icon/cursor image"; } + IntOrString getResourceType() const override { + return Type == IconCursorGroupType::Icon ? RkSingleIcon : RkSingleCursor; + } + ResourceKind getKind() const override { return RkSingleCursorOrIconRes; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkSingleCursorOrIconRes; + } +}; + +class IconCursorGroupResource : public RCResource { +public: + IconCursorGroupType Type; + GroupIconDir Header; + std::vector<ResourceDirEntryStart> ItemEntries; + + IconCursorGroupResource(IconCursorGroupType ResourceType, + const GroupIconDir &HeaderData, + std::vector<ResourceDirEntryStart> &&Entries) + : Type(ResourceType), Header(HeaderData), + ItemEntries(std::move(Entries)) {} + + Twine getResourceTypeName() const override { return "Icon/cursor group"; } + IntOrString getResourceType() const override { + return Type == IconCursorGroupType::Icon ? RkIconGroup : RkCursorGroup; + } + ResourceKind getKind() const override { return RkCursorOrIconGroupRes; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkCursorOrIconGroupRes; + } +}; + +Error ResourceFileWriter::writeSingleIconOrCursorBody(const RCResource *Base) { + auto *Res = cast<SingleIconCursorResource>(Base); + if (Res->Type == IconCursorGroupType::Cursor) { + // In case of cursors, two WORDS are appended to the beginning + // of the resource: HotspotX (Planes in RESDIRENTRY), + // and HotspotY (BitCount). + // + // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026.aspx + // (Remarks section). + writeObject(Res->Header.Planes); + writeObject(Res->Header.BitCount); + } + + writeObject(Res->Image); + return Error::success(); +} + +Error ResourceFileWriter::writeIconOrCursorGroupBody(const RCResource *Base) { + auto *Res = cast<IconCursorGroupResource>(Base); + writeObject(Res->Header); + for (auto Item : Res->ItemEntries) { + writeObject(Item); + writeInt(IconCursorID++); + } + return Error::success(); +} + +Error ResourceFileWriter::visitSingleIconOrCursor(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeSingleIconOrCursorBody); +} + +Error ResourceFileWriter::visitIconOrCursorGroup(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeIconOrCursorGroupBody); +} + +Error ResourceFileWriter::visitIconOrCursorResource(const RCResource *Base) { + IconCursorGroupType Type; + StringRef FileStr; + IntOrString ResName = Base->ResName; + + if (auto *IconRes = dyn_cast<IconResource>(Base)) { + FileStr = IconRes->IconLoc; + Type = IconCursorGroupType::Icon; + } else { + auto *CursorRes = dyn_cast<CursorResource>(Base); + FileStr = CursorRes->CursorLoc; + Type = IconCursorGroupType::Cursor; + } + + bool IsLong; + stripQuotes(FileStr, IsLong); + auto File = loadFile(FileStr); + + if (!File) + return File.takeError(); + + BinaryStreamReader Reader((*File)->getBuffer(), support::little); + + // Read the file headers. + // - At the beginning, ICONDIR/NEWHEADER header. + // - Then, a number of RESDIR headers follow. These contain offsets + // to data. + const GroupIconDir *Header; + + RETURN_IF_ERROR(Reader.readObject(Header)); + if (Header->Reserved != 0) + return createError("Incorrect icon/cursor Reserved field; should be 0."); + uint16_t NeededType = Type == IconCursorGroupType::Icon ? 1 : 2; + if (Header->ResType != NeededType) + return createError("Incorrect icon/cursor ResType field; should be " + + Twine(NeededType) + "."); + + uint16_t NumItems = Header->ResCount; + + // Read single ico/cur headers. + std::vector<ResourceDirEntryStart> ItemEntries; + ItemEntries.reserve(NumItems); + std::vector<uint32_t> ItemOffsets(NumItems); + for (size_t ID = 0; ID < NumItems; ++ID) { + const ResourceDirEntryStart *Object; + RETURN_IF_ERROR(Reader.readObject(Object)); + ItemEntries.push_back(*Object); + RETURN_IF_ERROR(Reader.readInteger(ItemOffsets[ID])); + } + + // Now write each icon/cursors one by one. At first, all the contents + // without ICO/CUR header. This is described by SingleIconCursorResource. + for (size_t ID = 0; ID < NumItems; ++ID) { + // Load the fragment of file. + Reader.setOffset(ItemOffsets[ID]); + ArrayRef<uint8_t> Image; + RETURN_IF_ERROR(Reader.readArray(Image, ItemEntries[ID].Size)); + SingleIconCursorResource SingleRes(Type, ItemEntries[ID], Image, + Base->MemoryFlags); + SingleRes.setName(IconCursorID + ID); + RETURN_IF_ERROR(visitSingleIconOrCursor(&SingleRes)); + } + + // Now, write all the headers concatenated into a separate resource. + for (size_t ID = 0; ID < NumItems; ++ID) { + // We need to rewrite the cursor headers, and fetch actual values + // for Planes/BitCount. + const auto &OldHeader = ItemEntries[ID]; + ResourceDirEntryStart NewHeader = OldHeader; + + if (Type == IconCursorGroupType::Cursor) { + NewHeader.Cursor.Width = OldHeader.Icon.Width; + // Each cursor in fact stores two bitmaps, one under another. + // Height provided in cursor definition describes the height of the + // cursor, whereas the value existing in resource definition describes + // the height of the bitmap. Therefore, we need to double this height. + NewHeader.Cursor.Height = OldHeader.Icon.Height * 2; + + // Two WORDs were written at the beginning of the resource (hotspot + // location). This is reflected in Size field. + NewHeader.Size += 2 * sizeof(uint16_t); + } + + // Now, we actually need to read the bitmap header to find + // the number of planes and the number of bits per pixel. + Reader.setOffset(ItemOffsets[ID]); + const BitmapInfoHeader *BMPHeader; + RETURN_IF_ERROR(Reader.readObject(BMPHeader)); + if (BMPHeader->Size == sizeof(BitmapInfoHeader)) { + NewHeader.Planes = BMPHeader->Planes; + NewHeader.BitCount = BMPHeader->BitCount; + } else { + // A PNG .ico file. + // https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473 + // "The image must be in 32bpp" + NewHeader.Planes = 1; + NewHeader.BitCount = 32; + } + + ItemEntries[ID] = NewHeader; + } + + IconCursorGroupResource HeaderRes(Type, *Header, std::move(ItemEntries)); + HeaderRes.setName(ResName); + if (Base->MemoryFlags & MfPreload) { + HeaderRes.MemoryFlags |= MfPreload; + HeaderRes.MemoryFlags &= ~MfPure; + } + RETURN_IF_ERROR(visitIconOrCursorGroup(&HeaderRes)); + + return Error::success(); +} + +// --- DialogResource helpers. --- // + +Error ResourceFileWriter::writeSingleDialogControl(const Control &Ctl, + bool IsExtended) { + // Each control should be aligned to DWORD. + padStream(sizeof(uint32_t)); + + auto TypeInfo = Control::SupportedCtls.lookup(Ctl.Type); + IntWithNotMask CtlStyle(TypeInfo.Style); + CtlStyle |= Ctl.Style.getValueOr(RCInt(0)); + uint32_t CtlExtStyle = Ctl.ExtStyle.getValueOr(0); + + // DIALOG(EX) item header prefix. + if (!IsExtended) { + struct { + ulittle32_t Style; + ulittle32_t ExtStyle; + } Prefix{ulittle32_t(CtlStyle.getValue()), ulittle32_t(CtlExtStyle)}; + writeObject(Prefix); + } else { + struct { + ulittle32_t HelpID; + ulittle32_t ExtStyle; + ulittle32_t Style; + } Prefix{ulittle32_t(Ctl.HelpID.getValueOr(0)), ulittle32_t(CtlExtStyle), + ulittle32_t(CtlStyle.getValue())}; + writeObject(Prefix); + } + + // Common fixed-length part. + RETURN_IF_ERROR(checkSignedNumberFits<int16_t>( + Ctl.X, "Dialog control x-coordinate", true)); + RETURN_IF_ERROR(checkSignedNumberFits<int16_t>( + Ctl.Y, "Dialog control y-coordinate", true)); + RETURN_IF_ERROR( + checkSignedNumberFits<int16_t>(Ctl.Width, "Dialog control width", false)); + RETURN_IF_ERROR(checkSignedNumberFits<int16_t>( + Ctl.Height, "Dialog control height", false)); + struct { + ulittle16_t X; + ulittle16_t Y; + ulittle16_t Width; + ulittle16_t Height; + } Middle{ulittle16_t(Ctl.X), ulittle16_t(Ctl.Y), ulittle16_t(Ctl.Width), + ulittle16_t(Ctl.Height)}; + writeObject(Middle); + + // ID; it's 16-bit in DIALOG and 32-bit in DIALOGEX. + if (!IsExtended) { + // It's common to use -1, i.e. UINT32_MAX, for controls one doesn't + // want to refer to later. + if (Ctl.ID != static_cast<uint32_t>(-1)) + RETURN_IF_ERROR(checkNumberFits<uint16_t>( + Ctl.ID, "Control ID in simple DIALOG resource")); + writeInt<uint16_t>(Ctl.ID); + } else { + writeInt<uint32_t>(Ctl.ID); + } + + // Window class - either 0xFFFF + 16-bit integer or a string. + RETURN_IF_ERROR(writeIntOrString(Ctl.Class)); + + // Element caption/reference ID. ID is preceded by 0xFFFF. + RETURN_IF_ERROR(checkIntOrString(Ctl.Title, "Control reference ID")); + RETURN_IF_ERROR(writeIntOrString(Ctl.Title)); + + // # bytes of extra creation data count. Don't pass any. + writeInt<uint16_t>(0); + + return Error::success(); +} + +Error ResourceFileWriter::writeDialogBody(const RCResource *Base) { + auto *Res = cast<DialogResource>(Base); + + // Default style: WS_POPUP | WS_BORDER | WS_SYSMENU. + const uint32_t DefaultStyle = 0x80880000; + const uint32_t StyleFontFlag = 0x40; + const uint32_t StyleCaptionFlag = 0x00C00000; + + uint32_t UsedStyle = ObjectData.Style.getValueOr(DefaultStyle); + if (ObjectData.Font) + UsedStyle |= StyleFontFlag; + else + UsedStyle &= ~StyleFontFlag; + + // Actually, in case of empty (but existent) caption, the examined field + // is equal to "\"\"". That's why empty captions are still noticed. + if (ObjectData.Caption != "") + UsedStyle |= StyleCaptionFlag; + + const uint16_t DialogExMagic = 0xFFFF; + uint32_t ExStyle = ObjectData.ExStyle.getValueOr(0); + + // Write DIALOG(EX) header prefix. These are pretty different. + if (!Res->IsExtended) { + // We cannot let the higher word of DefaultStyle be equal to 0xFFFF. + // In such a case, whole object (in .res file) is equivalent to a + // DIALOGEX. It might lead to access violation/segmentation fault in + // resource readers. For example, + // 1 DIALOG 0, 0, 0, 65432 + // STYLE 0xFFFF0001 {} + // would be compiled to a DIALOGEX with 65432 controls. + if ((UsedStyle >> 16) == DialogExMagic) + return createError("16 higher bits of DIALOG resource style cannot be" + " equal to 0xFFFF"); + + struct { + ulittle32_t Style; + ulittle32_t ExtStyle; + } Prefix{ulittle32_t(UsedStyle), + ulittle32_t(ExStyle)}; + + writeObject(Prefix); + } else { + struct { + ulittle16_t Version; + ulittle16_t Magic; + ulittle32_t HelpID; + ulittle32_t ExtStyle; + ulittle32_t Style; + } Prefix{ulittle16_t(1), ulittle16_t(DialogExMagic), + ulittle32_t(Res->HelpID), ulittle32_t(ExStyle), ulittle32_t(UsedStyle)}; + + writeObject(Prefix); + } + + // Now, a common part. First, fixed-length fields. + RETURN_IF_ERROR(checkNumberFits<uint16_t>(Res->Controls.size(), + "Number of dialog controls")); + RETURN_IF_ERROR( + checkSignedNumberFits<int16_t>(Res->X, "Dialog x-coordinate", true)); + RETURN_IF_ERROR( + checkSignedNumberFits<int16_t>(Res->Y, "Dialog y-coordinate", true)); + RETURN_IF_ERROR( + checkSignedNumberFits<int16_t>(Res->Width, "Dialog width", false)); + RETURN_IF_ERROR( + checkSignedNumberFits<int16_t>(Res->Height, "Dialog height", false)); + struct { + ulittle16_t Count; + ulittle16_t PosX; + ulittle16_t PosY; + ulittle16_t DialogWidth; + ulittle16_t DialogHeight; + } Middle{ulittle16_t(Res->Controls.size()), ulittle16_t(Res->X), + ulittle16_t(Res->Y), ulittle16_t(Res->Width), + ulittle16_t(Res->Height)}; + writeObject(Middle); + + // MENU field. As of now, we don't keep them in the state and can peacefully + // think there is no menu attached to the dialog. + writeInt<uint16_t>(0); + + // Window CLASS field. + RETURN_IF_ERROR(writeIntOrString(ObjectData.Class)); + + // Window title or a single word equal to 0. + RETURN_IF_ERROR(writeCString(ObjectData.Caption)); + + // If there *is* a window font declared, output its data. + auto &Font = ObjectData.Font; + if (Font) { + writeInt<uint16_t>(Font->Size); + // Additional description occurs only in DIALOGEX. + if (Res->IsExtended) { + writeInt<uint16_t>(Font->Weight); + writeInt<uint8_t>(Font->IsItalic); + writeInt<uint8_t>(Font->Charset); + } + RETURN_IF_ERROR(writeCString(Font->Typeface)); + } + + auto handleCtlError = [&](Error &&Err, const Control &Ctl) -> Error { + if (!Err) + return Error::success(); + return joinErrors(createError("Error in " + Twine(Ctl.Type) + + " control (ID " + Twine(Ctl.ID) + "):"), + std::move(Err)); + }; + + for (auto &Ctl : Res->Controls) + RETURN_IF_ERROR( + handleCtlError(writeSingleDialogControl(Ctl, Res->IsExtended), Ctl)); + + return Error::success(); +} + +// --- HTMLResource helpers. --- // + +Error ResourceFileWriter::writeHTMLBody(const RCResource *Base) { + return appendFile(cast<HTMLResource>(Base)->HTMLLoc); +} + +// --- MenuResource helpers. --- // + +Error ResourceFileWriter::writeMenuDefinition( + const std::unique_ptr<MenuDefinition> &Def, uint16_t Flags) { + assert(Def); + const MenuDefinition *DefPtr = Def.get(); + + if (auto *MenuItemPtr = dyn_cast<MenuItem>(DefPtr)) { + writeInt<uint16_t>(Flags); + // Some resource files use -1, i.e. UINT32_MAX, for empty menu items. + if (MenuItemPtr->Id != static_cast<uint32_t>(-1)) + RETURN_IF_ERROR( + checkNumberFits<uint16_t>(MenuItemPtr->Id, "MENUITEM action ID")); + writeInt<uint16_t>(MenuItemPtr->Id); + RETURN_IF_ERROR(writeCString(MenuItemPtr->Name)); + return Error::success(); + } + + if (isa<MenuSeparator>(DefPtr)) { + writeInt<uint16_t>(Flags); + writeInt<uint32_t>(0); + return Error::success(); + } + + auto *PopupPtr = cast<PopupItem>(DefPtr); + writeInt<uint16_t>(Flags); + RETURN_IF_ERROR(writeCString(PopupPtr->Name)); + return writeMenuDefinitionList(PopupPtr->SubItems); +} + +Error ResourceFileWriter::writeMenuDefinitionList( + const MenuDefinitionList &List) { + for (auto &Def : List.Definitions) { + uint16_t Flags = Def->getResFlags(); + // Last element receives an additional 0x80 flag. + const uint16_t LastElementFlag = 0x0080; + if (&Def == &List.Definitions.back()) + Flags |= LastElementFlag; + + RETURN_IF_ERROR(writeMenuDefinition(Def, Flags)); + } + return Error::success(); +} + +Error ResourceFileWriter::writeMenuBody(const RCResource *Base) { + // At first, MENUHEADER structure. In fact, these are two WORDs equal to 0. + // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648018.aspx + writeInt<uint32_t>(0); + + return writeMenuDefinitionList(cast<MenuResource>(Base)->Elements); +} + +// --- StringTableResource helpers. --- // + +class BundleResource : public RCResource { +public: + using BundleType = ResourceFileWriter::StringTableInfo::Bundle; + BundleType Bundle; + + BundleResource(const BundleType &StrBundle) + : RCResource(StrBundle.MemoryFlags), Bundle(StrBundle) {} + IntOrString getResourceType() const override { return 6; } + + ResourceKind getKind() const override { return RkStringTableBundle; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkStringTableBundle; + } + Twine getResourceTypeName() const override { return "STRINGTABLE"; } +}; + +Error ResourceFileWriter::visitStringTableBundle(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeStringTableBundleBody); +} + +Error ResourceFileWriter::insertStringIntoBundle( + StringTableInfo::Bundle &Bundle, uint16_t StringID, + const std::vector<StringRef> &String) { + uint16_t StringLoc = StringID & 15; + if (Bundle.Data[StringLoc]) + return createError("Multiple STRINGTABLE strings located under ID " + + Twine(StringID)); + Bundle.Data[StringLoc] = String; + return Error::success(); +} + +Error ResourceFileWriter::writeStringTableBundleBody(const RCResource *Base) { + auto *Res = cast<BundleResource>(Base); + for (size_t ID = 0; ID < Res->Bundle.Data.size(); ++ID) { + // The string format is a tiny bit different here. We + // first output the size of the string, and then the string itself + // (which is not null-terminated). + SmallVector<UTF16, 128> Data; + if (Res->Bundle.Data[ID]) { + bool IsLongString; + for (StringRef S : *Res->Bundle.Data[ID]) + RETURN_IF_ERROR(processString(S, NullHandlingMethod::CutAtDoubleNull, + IsLongString, Data, Params.CodePage)); + if (AppendNull) + Data.push_back('\0'); + } + RETURN_IF_ERROR( + checkNumberFits<uint16_t>(Data.size(), "STRINGTABLE string size")); + writeInt<uint16_t>(Data.size()); + for (auto Char : Data) + writeInt(Char); + } + return Error::success(); +} + +Error ResourceFileWriter::dumpAllStringTables() { + for (auto Key : StringTableData.BundleList) { + auto Iter = StringTableData.BundleData.find(Key); + assert(Iter != StringTableData.BundleData.end()); + + // For a moment, revert the context info to moment of bundle declaration. + ContextKeeper RAII(this); + ObjectData = Iter->second.DeclTimeInfo; + + BundleResource Res(Iter->second); + // Bundle #(k+1) contains keys [16k, 16k + 15]. + Res.setName(Key.first + 1); + RETURN_IF_ERROR(visitStringTableBundle(&Res)); + } + return Error::success(); +} + +// --- UserDefinedResource helpers. --- // + +Error ResourceFileWriter::writeUserDefinedBody(const RCResource *Base) { + auto *Res = cast<UserDefinedResource>(Base); + + if (Res->IsFileResource) + return appendFile(Res->FileLoc); + + for (auto &Elem : Res->Contents) { + if (Elem.isInt()) { + RETURN_IF_ERROR( + checkRCInt(Elem.getInt(), "Number in user-defined resource")); + writeRCInt(Elem.getInt()); + continue; + } + + SmallVector<UTF16, 128> ProcessedString; + bool IsLongString; + RETURN_IF_ERROR( + processString(Elem.getString(), NullHandlingMethod::UserResource, + IsLongString, ProcessedString, Params.CodePage)); + + for (auto Ch : ProcessedString) { + if (IsLongString) { + writeInt(Ch); + continue; + } + + RETURN_IF_ERROR(checkNumberFits<uint8_t>( + Ch, "Character in narrow string in user-defined resource")); + writeInt<uint8_t>(Ch); + } + } + + return Error::success(); +} + +// --- VersionInfoResourceResource helpers. --- // + +Error ResourceFileWriter::writeVersionInfoBlock(const VersionInfoBlock &Blk) { + // Output the header if the block has name. + bool OutputHeader = Blk.Name != ""; + uint64_t LengthLoc; + + padStream(sizeof(uint32_t)); + if (OutputHeader) { + LengthLoc = writeInt<uint16_t>(0); + writeInt<uint16_t>(0); + writeInt<uint16_t>(1); // true + RETURN_IF_ERROR(writeCString(Blk.Name)); + padStream(sizeof(uint32_t)); + } + + for (const std::unique_ptr<VersionInfoStmt> &Item : Blk.Stmts) { + VersionInfoStmt *ItemPtr = Item.get(); + + if (auto *BlockPtr = dyn_cast<VersionInfoBlock>(ItemPtr)) { + RETURN_IF_ERROR(writeVersionInfoBlock(*BlockPtr)); + continue; + } + + auto *ValuePtr = cast<VersionInfoValue>(ItemPtr); + RETURN_IF_ERROR(writeVersionInfoValue(*ValuePtr)); + } + + if (OutputHeader) { + uint64_t CurLoc = tell(); + writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc); + } + + 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"); + + padStream(sizeof(uint32_t)); + auto LengthLoc = writeInt<uint16_t>(0); + auto ValLengthLoc = writeInt<uint16_t>(0); + writeInt<uint16_t>(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); + return Error::success(); +} + +template <typename Ty> +static Ty getWithDefault(const StringMap<Ty> &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<VersionInfoResource>(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 = writeInt<uint16_t>(0); + writeInt<uint16_t>(sizeof(FixedInfo)); + writeInt<uint16_t>(0); + cantFail(writeCString("VS_VERSION_INFO")); + padStream(sizeof(uint32_t)); + + using VersionInfoFixed = VersionInfoResource::VersionInfoFixed; + auto GetField = [&](VersionInfoFixed::VersionInfoFixedType Type) { + static const SmallVector<uint32_t, 4> 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<uint16_t>( + *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<uint16_t>( + *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(); +} + +Expected<std::unique_ptr<MemoryBuffer>> +ResourceFileWriter::loadFile(StringRef File) const { + SmallString<128> Path; + SmallString<128> Cwd; + std::unique_ptr<MemoryBuffer> Result; + + // 0. The file path is absolute or has a root directory, so we shouldn't + // try to append it on top of other base directories. (An absolute path + // must have a root directory, but e.g. the path "\dir\file" on windows + // isn't considered absolute, but it does have a root directory. As long as + // sys::path::append doesn't handle appending an absolute path or a path + // starting with a root directory on top of a base, we must handle this + // case separately at the top. C++17's path::append handles that case + // properly though, so if using that to append paths below, this early + // exception case could be removed.) + if (sys::path::has_root_directory(File)) + return errorOrToExpected(MemoryBuffer::getFile( + File, /*IsText=*/false, /*RequiresNullTerminator=*/false)); + + // 1. The current working directory. + sys::fs::current_path(Cwd); + Path.assign(Cwd.begin(), Cwd.end()); + sys::path::append(Path, File); + if (sys::fs::exists(Path)) + return errorOrToExpected(MemoryBuffer::getFile( + Path, /*IsText=*/false, /*RequiresNullTerminator=*/false)); + + // 2. The directory of the input resource file, if it is different from the + // current working directory. + StringRef InputFileDir = sys::path::parent_path(Params.InputFilePath); + Path.assign(InputFileDir.begin(), InputFileDir.end()); + sys::path::append(Path, File); + if (sys::fs::exists(Path)) + return errorOrToExpected(MemoryBuffer::getFile( + Path, /*IsText=*/false, /*RequiresNullTerminator=*/false)); + + // 3. All of the include directories specified on the command line. + for (StringRef ForceInclude : Params.Include) { + Path.assign(ForceInclude.begin(), ForceInclude.end()); + sys::path::append(Path, File); + if (sys::fs::exists(Path)) + return errorOrToExpected(MemoryBuffer::getFile( + Path, /*IsText=*/false, /*RequiresNullTerminator=*/false)); + } + + if (!Params.NoInclude) { + if (auto Result = llvm::sys::Process::FindInEnvPath("INCLUDE", File)) + return errorOrToExpected(MemoryBuffer::getFile( + *Result, /*IsText=*/false, /*RequiresNullTerminator=*/false)); + } + + return make_error<StringError>("error : file not found : " + Twine(File), + inconvertibleErrorCode()); +} + +} // namespace rc +} // namespace llvm diff --git a/contrib/libs/llvm14/tools/llvm-rc/ResourceFileWriter.h b/contrib/libs/llvm14/tools/llvm-rc/ResourceFileWriter.h new file mode 100644 index 0000000000..0f3d593725 --- /dev/null +++ b/contrib/libs/llvm14/tools/llvm-rc/ResourceFileWriter.h @@ -0,0 +1,217 @@ +//===-- ResourceSerializator.h ----------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This defines a visitor serializing resources to a .res stream. +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_LLVMRC_RESOURCESERIALIZATOR_H +#define LLVM_TOOLS_LLVMRC_RESOURCESERIALIZATOR_H + +#include "ResourceScriptStmt.h" +#include "ResourceVisitor.h" + +#include "llvm/Support/Endian.h" + +namespace llvm { + +class MemoryBuffer; + +namespace rc { + +enum CodePage { + CpAcp = 0, // The current used codepage. Since there's no such + // notion in LLVM what codepage it actually means, + // this only allows ASCII. + CpWin1252 = 1252, // A codepage where most 8 bit values correspond to + // unicode code points with the same value. + CpUtf8 = 65001, // UTF-8. +}; + +struct WriterParams { + std::vector<std::string> Include; // Additional folders to search for files. + bool NoInclude; // Ignore the INCLUDE variable. + StringRef InputFilePath; // The full path of the input file. + int CodePage = CpAcp; // The codepage for interpreting characters. +}; + +class ResourceFileWriter : public Visitor { +public: + ResourceFileWriter(const WriterParams &Params, + std::unique_ptr<raw_fd_ostream> Stream) + : Params(Params), FS(std::move(Stream)), IconCursorID(1) { + assert(FS && "Output stream needs to be provided to the serializator"); + } + + Error visitNullResource(const RCResource *) override; + Error visitAcceleratorsResource(const RCResource *) override; + Error visitCursorResource(const RCResource *) override; + Error visitDialogResource(const RCResource *) override; + Error visitHTMLResource(const RCResource *) override; + Error visitIconResource(const RCResource *) override; + Error visitMenuResource(const RCResource *) override; + Error visitVersionInfoResource(const RCResource *) override; + Error visitStringTableResource(const RCResource *) override; + Error visitUserDefinedResource(const RCResource *) override; + + Error visitCaptionStmt(const CaptionStmt *) override; + Error visitCharacteristicsStmt(const CharacteristicsStmt *) override; + Error visitClassStmt(const ClassStmt *) override; + Error visitExStyleStmt(const ExStyleStmt *) override; + Error visitFontStmt(const FontStmt *) override; + Error visitLanguageStmt(const LanguageResource *) override; + Error visitStyleStmt(const StyleStmt *) override; + Error visitVersionStmt(const VersionStmt *) override; + + // Stringtables are output at the end of .res file. We need a separate + // function to do it. + Error dumpAllStringTables(); + + bool AppendNull = false; // Append '\0' to each existing STRINGTABLE element? + + struct ObjectInfo { + uint16_t LanguageInfo; + uint32_t Characteristics; + uint32_t VersionInfo; + + Optional<uint32_t> Style; + Optional<uint32_t> ExStyle; + StringRef Caption; + struct FontInfo { + uint32_t Size; + StringRef Typeface; + uint32_t Weight; + bool IsItalic; + uint32_t Charset; + }; + Optional<FontInfo> Font; + IntOrString Class; + + ObjectInfo() + : LanguageInfo(0), Characteristics(0), VersionInfo(0), + Class(StringRef()) {} + } ObjectData; + + struct StringTableInfo { + // Each STRINGTABLE bundle depends on ID of the bundle and language + // description. + using BundleKey = std::pair<uint16_t, uint16_t>; + // Each bundle is in fact an array of 16 strings. + struct Bundle { + std::array<Optional<std::vector<StringRef>>, 16> Data; + ObjectInfo DeclTimeInfo; + uint16_t MemoryFlags; + Bundle(const ObjectInfo &Info, uint16_t Flags) + : DeclTimeInfo(Info), MemoryFlags(Flags) {} + }; + std::map<BundleKey, Bundle> BundleData; + // Bundles are listed in the order of their first occurrence. + std::vector<BundleKey> BundleList; + } StringTableData; + +private: + Error handleError(Error Err, const RCResource *Res); + + Error + writeResource(const RCResource *Res, + Error (ResourceFileWriter::*BodyWriter)(const RCResource *)); + + // NullResource + Error writeNullBody(const RCResource *); + + // AcceleratorsResource + Error writeSingleAccelerator(const AcceleratorsResource::Accelerator &, + bool IsLastItem); + Error writeAcceleratorsBody(const RCResource *); + + // BitmapResource + Error visitBitmapResource(const RCResource *) override; + Error writeBitmapBody(const RCResource *); + + // CursorResource and IconResource + Error visitIconOrCursorResource(const RCResource *); + Error visitIconOrCursorGroup(const RCResource *); + Error visitSingleIconOrCursor(const RCResource *); + Error writeSingleIconOrCursorBody(const RCResource *); + Error writeIconOrCursorGroupBody(const RCResource *); + + // DialogResource + Error writeSingleDialogControl(const Control &, bool IsExtended); + Error writeDialogBody(const RCResource *); + + // HTMLResource + Error writeHTMLBody(const RCResource *); + + // MenuResource + Error writeMenuDefinition(const std::unique_ptr<MenuDefinition> &, + uint16_t Flags); + Error writeMenuDefinitionList(const MenuDefinitionList &List); + Error writeMenuBody(const RCResource *); + + // StringTableResource + Error visitStringTableBundle(const RCResource *); + Error writeStringTableBundleBody(const RCResource *); + Error insertStringIntoBundle(StringTableInfo::Bundle &Bundle, + uint16_t StringID, + const std::vector<StringRef> &String); + + // User defined resource + Error writeUserDefinedBody(const RCResource *); + + // VersionInfoResource + Error writeVersionInfoBody(const RCResource *); + Error writeVersionInfoBlock(const VersionInfoBlock &); + Error writeVersionInfoValue(const VersionInfoValue &); + + const WriterParams &Params; + + // Output stream handling. + std::unique_ptr<raw_fd_ostream> FS; + + uint64_t tell() const { return FS->tell(); } + + uint64_t writeObject(const ArrayRef<uint8_t> Data); + + template <typename T> uint64_t writeInt(const T &Value) { + support::detail::packed_endian_specific_integral<T, support::little, + support::unaligned> + Object(Value); + return writeObject(Object); + } + + template <typename T> uint64_t writeObject(const T &Value) { + return writeObject(ArrayRef<uint8_t>( + reinterpret_cast<const uint8_t *>(&Value), sizeof(T))); + } + + template <typename T> void writeObjectAt(const T &Value, uint64_t Position) { + FS->pwrite((const char *)&Value, sizeof(T), Position); + } + + Error writeCString(StringRef Str, bool WriteTerminator = true); + + Error writeIdentifier(const IntOrString &Ident); + Error writeIntOrString(const IntOrString &Data); + + void writeRCInt(RCInt); + + Error appendFile(StringRef Filename); + + void padStream(uint64_t Length); + + Expected<std::unique_ptr<MemoryBuffer>> loadFile(StringRef File) const; + + // Icon and cursor IDs are allocated starting from 1 and increasing for + // each icon/cursor dumped. This maintains the current ID to be allocated. + uint16_t IconCursorID; +}; + +} // namespace rc +} // namespace llvm + +#endif diff --git a/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptCppFilter.cpp b/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptCppFilter.cpp new file mode 100644 index 0000000000..6657aa54cf --- /dev/null +++ b/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptCppFilter.cpp @@ -0,0 +1,111 @@ +//===-- ResourceScriptCppFilter.cpp ----------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This file implements an interface defined in ResourceScriptCppFilter.h. +// +//===---------------------------------------------------------------------===// + +#include "ResourceScriptCppFilter.h" +#include "llvm/ADT/StringExtras.h" + +#include <vector> + +using namespace llvm; + +namespace { + +class Filter { +public: + explicit Filter(StringRef Input) : Data(Input), DataLength(Input.size()) {} + + std::string run(); + +private: + // Parse the line, returning whether the line should be included in + // the output. + bool parseLine(StringRef Line); + + bool streamEof() const; + + StringRef Data; + size_t DataLength; + + size_t Pos = 0; + bool Outputting = true; +}; + +std::string Filter::run() { + std::vector<StringRef> Output; + + while (!streamEof() && Pos != StringRef::npos) { + size_t LineStart = Pos; + Pos = Data.find_first_of("\r\n", Pos); + Pos = Data.find_first_not_of("\r\n", Pos); + StringRef Line = Data.take_front(Pos).drop_front(LineStart); + + if (parseLine(Line)) + Output.push_back(Line); + } + + return llvm::join(Output, ""); +} + +bool Filter::parseLine(StringRef Line) { + Line = Line.ltrim(); + + if (!Line.consume_front("#")) { + // A normal content line, filtered according to the current mode. + return Outputting; + } + + // Found a preprocessing directive line. From here on, we always return + // false since the preprocessing directives should be filtered out. + + Line.consume_front("line"); + if (!Line.startswith(" ")) + return false; // Not a line directive (pragma etc). + + // #line 123 "path/file.h" + // # 123 "path/file.h" 1 + + Line = + Line.ltrim(); // There could be multiple spaces after the #line directive + + size_t N; + if (Line.consumeInteger(10, N)) // Returns true to signify an error + return false; + + Line = Line.ltrim(); + + if (!Line.consume_front("\"")) + return false; // Malformed line, no quote found. + + // Split the string at the last quote (in case the path name had + // escaped quotes as well). + Line = Line.rsplit('"').first; + + StringRef Ext = Line.rsplit('.').second; + + if (Ext.equals_insensitive("h") || Ext.equals_insensitive("c")) { + Outputting = false; + } else { + Outputting = true; + } + + return false; +} + +bool Filter::streamEof() const { return Pos == DataLength; } + +} // anonymous namespace + +namespace llvm { + +std::string filterCppOutput(StringRef Input) { return Filter(Input).run(); } + +} // namespace llvm diff --git a/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptCppFilter.h b/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptCppFilter.h new file mode 100644 index 0000000000..780db317c9 --- /dev/null +++ b/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptCppFilter.h @@ -0,0 +1,34 @@ +//===-- ResourceScriptCppFilter.h ------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This filters the input to llvm-rc for preprocessor markers, removing +// preprocessing directives that a preprocessor can output or leave behind. +// +// It also filters out any contribution from files named *.h or *.c, based +// on preprocessor line markers. When preprocessing RC files, the included +// headers can leave behind C declarations, that RC doesn't understand. +// Rc.exe simply discards anything that comes from files named *.h or *.h. +// +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa381033(v=vs.85).aspx +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_LLVMRC_RESOURCESCRIPTCPPFILTER_H +#define LLVM_TOOLS_LLVMRC_RESOURCESCRIPTCPPFILTER_H + +#include "llvm/ADT/StringRef.h" + +#include <string> + +namespace llvm { + +std::string filterCppOutput(StringRef Input); + +} // namespace llvm + +#endif diff --git a/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptParser.cpp b/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptParser.cpp new file mode 100644 index 0000000000..7cb4d02e3c --- /dev/null +++ b/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptParser.cpp @@ -0,0 +1,856 @@ +//===-- ResourceScriptParser.cpp --------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This implements the parser defined in ResourceScriptParser.h. +// +//===---------------------------------------------------------------------===// + +#include "ResourceScriptParser.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Process.h" + +// Take an expression returning llvm::Error and forward the error if it exists. +#define RETURN_IF_ERROR(Expr) \ + if (auto Err = (Expr)) \ + return std::move(Err); + +// Take an expression returning llvm::Expected<T> and assign it to Var or +// forward the error out of the function. +#define ASSIGN_OR_RETURN(Var, Expr) \ + auto Var = (Expr); \ + if (!Var) \ + return Var.takeError(); + +namespace llvm { +namespace rc { + +RCParser::ParserError::ParserError(const Twine &Expected, const LocIter CurLoc, + const LocIter End) + : ErrorLoc(CurLoc), FileEnd(End) { + CurMessage = "Error parsing file: expected " + Expected.str() + ", got " + + (CurLoc == End ? "<EOF>" : CurLoc->value()).str(); +} + +char RCParser::ParserError::ID = 0; + +RCParser::RCParser(std::vector<RCToken> TokenList) + : Tokens(std::move(TokenList)), CurLoc(Tokens.begin()), End(Tokens.end()) {} + +bool RCParser::isEof() const { return CurLoc == End; } + +RCParser::ParseType RCParser::parseSingleResource() { + // The first thing we read is usually a resource's name. However, in some + // cases (LANGUAGE and STRINGTABLE) the resources don't have their names + // and the first token to be read is the type. + ASSIGN_OR_RETURN(NameToken, readTypeOrName()); + + if (NameToken->equalsLower("LANGUAGE")) + return parseLanguageResource(); + else if (NameToken->equalsLower("STRINGTABLE")) + return parseStringTableResource(); + + // If it's not an unnamed resource, what we've just read is a name. Now, + // read resource type; + ASSIGN_OR_RETURN(TypeToken, readTypeOrName()); + + ParseType Result = std::unique_ptr<RCResource>(); + (void)!Result; + + if (TypeToken->equalsLower("ACCELERATORS")) + Result = parseAcceleratorsResource(); + else if (TypeToken->equalsLower("BITMAP")) + Result = parseBitmapResource(); + else if (TypeToken->equalsLower("CURSOR")) + Result = parseCursorResource(); + else if (TypeToken->equalsLower("DIALOG")) + Result = parseDialogResource(false); + else if (TypeToken->equalsLower("DIALOGEX")) + Result = parseDialogResource(true); + else if (TypeToken->equalsLower("HTML")) + Result = parseHTMLResource(); + else if (TypeToken->equalsLower("ICON")) + Result = parseIconResource(); + else if (TypeToken->equalsLower("MENU")) + Result = parseMenuResource(); + else if (TypeToken->equalsLower("RCDATA")) + Result = parseUserDefinedResource(RkRcData); + else if (TypeToken->equalsLower("VERSIONINFO")) + Result = parseVersionInfoResource(); + else + Result = parseUserDefinedResource(*TypeToken); + + if (Result) + (*Result)->setName(*NameToken); + + return Result; +} + +bool RCParser::isNextTokenKind(Kind TokenKind) const { + return !isEof() && look().kind() == TokenKind; +} + +const RCToken &RCParser::look() const { + assert(!isEof()); + return *CurLoc; +} + +const RCToken &RCParser::read() { + assert(!isEof()); + return *CurLoc++; +} + +void RCParser::consume() { + assert(!isEof()); + CurLoc++; +} + +// An integer description might consist of a single integer or +// an arithmetic expression evaluating to the integer. The expressions +// can contain the following tokens: <int> ( ) + - | & ~ not. Their meaning +// is the same as in C++ except for 'not' expression. +// The operators in the original RC implementation have the following +// precedence: +// 1) Unary operators (- ~ not), +// 2) Binary operators (+ - & |), with no precedence. +// +// 'not' expression is mostly useful for style values. It evaluates to 0, +// but value given to the operator is stored separately from integer value. +// It's mostly useful for control style expressions and causes bits from +// default control style to be excluded from generated style. For binary +// operators the mask from the right operand is applied to the left operand +// and masks from both operands are combined in operator result. +// +// The following grammar is used to parse the expressions Exp1: +// Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2 +// Exp2 ::= -Exp2 || ~Exp2 || not Expr2 || Int || (Exp1). +// (More conveniently, Exp1 is a non-empty sequence of Exp2 expressions, +// separated by binary operators.) +// +// Expressions of type Exp1 are read by parseIntExpr1(Inner) method, while Exp2 +// is read by parseIntExpr2(). +// +// The original Microsoft tool handles multiple unary operators incorrectly. +// For example, in 16-bit little-endian integers: +// 1 => 01 00, -1 => ff ff, --1 => ff ff, ---1 => 01 00; +// 1 => 01 00, ~1 => fe ff, ~~1 => fd ff, ~~~1 => fc ff. +// Our implementation differs from the original one and handles these +// operators correctly: +// 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<RCInt> RCParser::readInt() { + ASSIGN_OR_RETURN(Value, parseIntExpr1()); + return (*Value).getValue(); +} + +Expected<IntWithNotMask> RCParser::parseIntExpr1() { + // Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2. + ASSIGN_OR_RETURN(FirstResult, parseIntExpr2()); + IntWithNotMask Result = *FirstResult; + + while (!isEof() && look().isBinaryOp()) { + auto OpToken = read(); + ASSIGN_OR_RETURN(NextResult, parseIntExpr2()); + + switch (OpToken.kind()) { + case Kind::Plus: + Result += *NextResult; + break; + + case Kind::Minus: + Result -= *NextResult; + break; + + case Kind::Pipe: + Result |= *NextResult; + break; + + case Kind::Amp: + Result &= *NextResult; + break; + + default: + llvm_unreachable("Already processed all binary ops."); + } + } + + return Result; +} + +Expected<IntWithNotMask> RCParser::parseIntExpr2() { + // Exp2 ::= -Exp2 || ~Exp2 || not Expr2 || Int || (Exp1). + static const char ErrorMsg[] = "'-', '~', integer or '('"; + + if (isEof()) + return getExpectedError(ErrorMsg); + + switch (look().kind()) { + case Kind::Minus: { + consume(); + ASSIGN_OR_RETURN(Result, parseIntExpr2()); + return -(*Result); + } + + case Kind::Tilde: { + consume(); + ASSIGN_OR_RETURN(Result, parseIntExpr2()); + return ~(*Result); + } + + case Kind::Int: + return RCInt(read()); + + case Kind::LeftParen: { + consume(); + ASSIGN_OR_RETURN(Result, parseIntExpr1()); + RETURN_IF_ERROR(consumeType(Kind::RightParen)); + return *Result; + } + + case Kind::Identifier: { + if (!read().value().equals_insensitive("not")) + return getExpectedError(ErrorMsg, true); + ASSIGN_OR_RETURN(Result, parseIntExpr2()); + return IntWithNotMask(0, (*Result).getValue()); + } + + default: + return getExpectedError(ErrorMsg); + } +} + +Expected<StringRef> RCParser::readString() { + if (!isNextTokenKind(Kind::String)) + return getExpectedError("string"); + return read().value(); +} + +Expected<StringRef> RCParser::readFilename() { + if (!isNextTokenKind(Kind::String) && !isNextTokenKind(Kind::Identifier)) + return getExpectedError("string"); + return read().value(); +} + +Expected<StringRef> RCParser::readIdentifier() { + if (!isNextTokenKind(Kind::Identifier)) + return getExpectedError("identifier"); + return read().value(); +} + +Expected<IntOrString> RCParser::readIntOrString() { + if (!isNextTokenKind(Kind::Int) && !isNextTokenKind(Kind::String)) + return getExpectedError("int or string"); + return IntOrString(read()); +} + +Expected<IntOrString> RCParser::readTypeOrName() { + // We suggest that the correct resource name or type should be either an + // identifier or an integer. The original RC tool is much more liberal. + if (!isNextTokenKind(Kind::Identifier) && !isNextTokenKind(Kind::Int)) + return getExpectedError("int or identifier"); + return IntOrString(read()); +} + +Error RCParser::consumeType(Kind TokenKind) { + if (isNextTokenKind(TokenKind)) { + consume(); + return Error::success(); + } + + switch (TokenKind) { +#define TOKEN(TokenName) \ + case Kind::TokenName: \ + return getExpectedError(#TokenName); +#define SHORT_TOKEN(TokenName, TokenCh) \ + case Kind::TokenName: \ + return getExpectedError(#TokenCh); +#include "ResourceScriptTokenList.def" + } + + llvm_unreachable("All case options exhausted."); +} + +bool RCParser::consumeOptionalType(Kind TokenKind) { + if (isNextTokenKind(TokenKind)) { + consume(); + return true; + } + + return false; +} + +Expected<SmallVector<RCInt, 8>> RCParser::readIntsWithCommas(size_t MinCount, + size_t MaxCount) { + assert(MinCount <= MaxCount); + + SmallVector<RCInt, 8> Result; + + auto FailureHandler = + [&](llvm::Error Err) -> Expected<SmallVector<RCInt, 8>> { + if (Result.size() < MinCount) + return std::move(Err); + consumeError(std::move(Err)); + return Result; + }; + + for (size_t i = 0; i < MaxCount; ++i) { + // Try to read a comma unless we read the first token. + // Sometimes RC tool requires them and sometimes not. We decide to + // always require them. + if (i >= 1) { + if (auto CommaError = consumeType(Kind::Comma)) + return FailureHandler(std::move(CommaError)); + } + + if (auto IntResult = readInt()) + Result.push_back(*IntResult); + else + return FailureHandler(IntResult.takeError()); + } + + return std::move(Result); +} + +Expected<uint32_t> RCParser::parseFlags(ArrayRef<StringRef> FlagDesc, + ArrayRef<uint32_t> FlagValues) { + assert(!FlagDesc.empty()); + assert(FlagDesc.size() == FlagValues.size()); + + 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_insensitive(FlagDesc[FlagId])) + continue; + + Result |= FlagValues[FlagId]; + FoundFlag = true; + break; + } + + if (!FoundFlag) + return getExpectedError(join(FlagDesc, "/"), true); + } + + return Result; +} + +uint16_t RCParser::parseMemoryFlags(uint16_t Flags) { + while (!isEof()) { + const RCToken &Token = look(); + if (Token.kind() != Kind::Identifier) + return Flags; + const StringRef Ident = Token.value(); + if (Ident.equals_insensitive("PRELOAD")) + Flags |= MfPreload; + else if (Ident.equals_insensitive("LOADONCALL")) + Flags &= ~MfPreload; + else if (Ident.equals_insensitive("FIXED")) + Flags &= ~(MfMoveable | MfDiscardable); + else if (Ident.equals_insensitive("MOVEABLE")) + Flags |= MfMoveable; + else if (Ident.equals_insensitive("DISCARDABLE")) + Flags |= MfDiscardable | MfMoveable | MfPure; + else if (Ident.equals_insensitive("PURE")) + Flags |= MfPure; + else if (Ident.equals_insensitive("IMPURE")) + Flags &= ~(MfPure | MfDiscardable); + else if (Ident.equals_insensitive("SHARED")) + Flags |= MfPure; + else if (Ident.equals_insensitive("NONSHARED")) + Flags &= ~(MfPure | MfDiscardable); + else + return Flags; + consume(); + } + return Flags; +} + +Expected<OptionalStmtList> +RCParser::parseOptionalStatements(OptStmtType StmtsType) { + OptionalStmtList Result; + + // The last statement is always followed by the start of the block. + while (!isNextTokenKind(Kind::BlockBegin)) { + ASSIGN_OR_RETURN(SingleParse, parseSingleOptionalStatement(StmtsType)); + Result.addStmt(std::move(*SingleParse)); + } + + return std::move(Result); +} + +Expected<std::unique_ptr<OptionalStmt>> +RCParser::parseSingleOptionalStatement(OptStmtType StmtsType) { + ASSIGN_OR_RETURN(TypeToken, readIdentifier()); + if (TypeToken->equals_insensitive("CHARACTERISTICS")) + return parseCharacteristicsStmt(); + if (TypeToken->equals_insensitive("LANGUAGE")) + return parseLanguageStmt(); + if (TypeToken->equals_insensitive("VERSION")) + return parseVersionStmt(); + + if (StmtsType != OptStmtType::BasicStmt) { + if (TypeToken->equals_insensitive("CAPTION")) + return parseCaptionStmt(); + if (TypeToken->equals_insensitive("CLASS")) + return parseClassStmt(); + if (TypeToken->equals_insensitive("EXSTYLE")) + return parseExStyleStmt(); + if (TypeToken->equals_insensitive("FONT")) + return parseFontStmt(StmtsType); + if (TypeToken->equals_insensitive("STYLE")) + return parseStyleStmt(); + } + + return getExpectedError("optional statement type, BEGIN or '{'", + /* IsAlreadyRead = */ true); +} + +RCParser::ParseType RCParser::parseLanguageResource() { + // Read LANGUAGE as an optional statement. If it's read correctly, we can + // upcast it to RCResource. + return parseLanguageStmt(); +} + +RCParser::ParseType RCParser::parseAcceleratorsResource() { + uint16_t MemoryFlags = + parseMemoryFlags(AcceleratorsResource::getDefaultMemoryFlags()); + ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements()); + RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); + + auto Accels = std::make_unique<AcceleratorsResource>( + std::move(*OptStatements), MemoryFlags); + + while (!consumeOptionalType(Kind::BlockEnd)) { + ASSIGN_OR_RETURN(EventResult, readIntOrString()); + RETURN_IF_ERROR(consumeType(Kind::Comma)); + ASSIGN_OR_RETURN(IDResult, readInt()); + ASSIGN_OR_RETURN( + FlagsResult, + parseFlags(AcceleratorsResource::Accelerator::OptionsStr, + AcceleratorsResource::Accelerator::OptionsFlags)); + Accels->addAccelerator(*EventResult, *IDResult, *FlagsResult); + } + + return std::move(Accels); +} + +RCParser::ParseType RCParser::parseCursorResource() { + uint16_t MemoryFlags = + parseMemoryFlags(CursorResource::getDefaultMemoryFlags()); + ASSIGN_OR_RETURN(Arg, readFilename()); + return std::make_unique<CursorResource>(*Arg, MemoryFlags); +} + +RCParser::ParseType RCParser::parseDialogResource(bool IsExtended) { + uint16_t MemoryFlags = + parseMemoryFlags(DialogResource::getDefaultMemoryFlags()); + // Dialog resources have the following format of the arguments: + // DIALOG: x, y, width, height [opt stmts...] {controls...} + // DIALOGEX: x, y, width, height [, helpID] [opt stmts...] {controls...} + // These are very similar, so we parse them together. + ASSIGN_OR_RETURN(LocResult, readIntsWithCommas(4, 4)); + + uint32_t HelpID = 0; // When HelpID is unset, it's assumed to be 0. + if (IsExtended && consumeOptionalType(Kind::Comma)) { + ASSIGN_OR_RETURN(HelpIDResult, readInt()); + HelpID = *HelpIDResult; + } + + ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements( + IsExtended ? OptStmtType::DialogExStmt + : OptStmtType::DialogStmt)); + + assert(isNextTokenKind(Kind::BlockBegin) && + "parseOptionalStatements, when successful, halts on BlockBegin."); + consume(); + + auto Dialog = std::make_unique<DialogResource>( + (*LocResult)[0], (*LocResult)[1], (*LocResult)[2], (*LocResult)[3], + HelpID, std::move(*OptStatements), IsExtended, MemoryFlags); + + while (!consumeOptionalType(Kind::BlockEnd)) { + ASSIGN_OR_RETURN(ControlDefResult, parseControl()); + Dialog->addControl(std::move(*ControlDefResult)); + } + + return std::move(Dialog); +} + +RCParser::ParseType RCParser::parseUserDefinedResource(IntOrString Type) { + uint16_t MemoryFlags = + parseMemoryFlags(UserDefinedResource::getDefaultMemoryFlags()); + if (isEof()) + return getExpectedError("filename, '{' or BEGIN"); + + // Check if this is a file resource. + switch (look().kind()) { + case Kind::String: + case Kind::Identifier: + return std::make_unique<UserDefinedResource>(Type, read().value(), + MemoryFlags); + default: + break; + } + + RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); + std::vector<IntOrString> Data; + + while (!consumeOptionalType(Kind::BlockEnd)) { + ASSIGN_OR_RETURN(Item, readIntOrString()); + Data.push_back(*Item); + + // There can be zero or more commas after each token (but not before + // the first one). + while (consumeOptionalType(Kind::Comma)) { + } + } + + return std::make_unique<UserDefinedResource>(Type, std::move(Data), + MemoryFlags); +} + +RCParser::ParseType RCParser::parseVersionInfoResource() { + uint16_t MemoryFlags = + parseMemoryFlags(VersionInfoResource::getDefaultMemoryFlags()); + ASSIGN_OR_RETURN(FixedResult, parseVersionInfoFixed()); + ASSIGN_OR_RETURN(BlockResult, parseVersionInfoBlockContents(StringRef())); + return std::make_unique<VersionInfoResource>( + std::move(**BlockResult), std::move(*FixedResult), MemoryFlags); +} + +Expected<Control> RCParser::parseControl() { + // Each control definition (except CONTROL) follows one of the schemes below + // depending on the control class: + // [class] text, id, x, y, width, height [, style] [, exstyle] [, helpID] + // [class] id, x, y, width, height [, style] [, exstyle] [, helpID] + // Note that control ids must be integers. + // Text might be either a string or an integer pointing to resource ID. + ASSIGN_OR_RETURN(ClassResult, readIdentifier()); + std::string ClassUpper = ClassResult->upper(); + auto CtlInfo = Control::SupportedCtls.find(ClassUpper); + if (CtlInfo == Control::SupportedCtls.end()) + return getExpectedError("control type, END or '}'", true); + + // Read caption if necessary. + IntOrString Caption{StringRef()}; + if (CtlInfo->getValue().HasTitle) { + ASSIGN_OR_RETURN(CaptionResult, readIntOrString()); + RETURN_IF_ERROR(consumeType(Kind::Comma)); + Caption = *CaptionResult; + } + + ASSIGN_OR_RETURN(ID, readInt()); + RETURN_IF_ERROR(consumeType(Kind::Comma)); + + IntOrString Class; + Optional<IntWithNotMask> Style; + if (ClassUpper == "CONTROL") { + // CONTROL text, id, class, style, x, y, width, height [, exstyle] [, helpID] + ASSIGN_OR_RETURN(ClassStr, readString()); + RETURN_IF_ERROR(consumeType(Kind::Comma)); + Class = *ClassStr; + ASSIGN_OR_RETURN(StyleVal, parseIntExpr1()); + RETURN_IF_ERROR(consumeType(Kind::Comma)); + Style = *StyleVal; + } else { + Class = CtlInfo->getValue().CtlClass; + } + + // x, y, width, height + ASSIGN_OR_RETURN(Args, readIntsWithCommas(4, 4)); + + if (ClassUpper != "CONTROL") { + if (consumeOptionalType(Kind::Comma)) { + ASSIGN_OR_RETURN(Val, parseIntExpr1()); + Style = *Val; + } + } + + Optional<uint32_t> ExStyle; + if (consumeOptionalType(Kind::Comma)) { + ASSIGN_OR_RETURN(Val, readInt()); + ExStyle = *Val; + } + Optional<uint32_t> HelpID; + if (consumeOptionalType(Kind::Comma)) { + ASSIGN_OR_RETURN(Val, readInt()); + HelpID = *Val; + } + + return Control(*ClassResult, Caption, *ID, (*Args)[0], (*Args)[1], + (*Args)[2], (*Args)[3], Style, ExStyle, HelpID, Class); +} + +RCParser::ParseType RCParser::parseBitmapResource() { + uint16_t MemoryFlags = + parseMemoryFlags(BitmapResource::getDefaultMemoryFlags()); + ASSIGN_OR_RETURN(Arg, readFilename()); + return std::make_unique<BitmapResource>(*Arg, MemoryFlags); +} + +RCParser::ParseType RCParser::parseIconResource() { + uint16_t MemoryFlags = + parseMemoryFlags(IconResource::getDefaultMemoryFlags()); + ASSIGN_OR_RETURN(Arg, readFilename()); + return std::make_unique<IconResource>(*Arg, MemoryFlags); +} + +RCParser::ParseType RCParser::parseHTMLResource() { + uint16_t MemoryFlags = + parseMemoryFlags(HTMLResource::getDefaultMemoryFlags()); + ASSIGN_OR_RETURN(Arg, readFilename()); + return std::make_unique<HTMLResource>(*Arg, MemoryFlags); +} + +RCParser::ParseType RCParser::parseMenuResource() { + uint16_t MemoryFlags = + parseMemoryFlags(MenuResource::getDefaultMemoryFlags()); + ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements()); + ASSIGN_OR_RETURN(Items, parseMenuItemsList()); + return std::make_unique<MenuResource>(std::move(*OptStatements), + std::move(*Items), MemoryFlags); +} + +Expected<MenuDefinitionList> RCParser::parseMenuItemsList() { + RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); + + MenuDefinitionList List; + + // Read a set of items. Each item is of one of three kinds: + // MENUITEM SEPARATOR + // MENUITEM caption:String, result:Int [, menu flags]... + // POPUP caption:String [, menu flags]... { items... } + while (!consumeOptionalType(Kind::BlockEnd)) { + ASSIGN_OR_RETURN(ItemTypeResult, readIdentifier()); + + bool IsMenuItem = ItemTypeResult->equals_insensitive("MENUITEM"); + bool IsPopup = ItemTypeResult->equals_insensitive("POPUP"); + if (!IsMenuItem && !IsPopup) + return getExpectedError("MENUITEM, POPUP, END or '}'", true); + + if (IsMenuItem && isNextTokenKind(Kind::Identifier)) { + // Now, expecting SEPARATOR. + ASSIGN_OR_RETURN(SeparatorResult, readIdentifier()); + if (SeparatorResult->equals_insensitive("SEPARATOR")) { + List.addDefinition(std::make_unique<MenuSeparator>()); + continue; + } + + return getExpectedError("SEPARATOR or string", true); + } + + // Not a separator. Read the caption. + ASSIGN_OR_RETURN(CaptionResult, readString()); + + // If MENUITEM, expect also a comma and an integer. + uint32_t MenuResult = -1; + + if (IsMenuItem) { + RETURN_IF_ERROR(consumeType(Kind::Comma)); + ASSIGN_OR_RETURN(IntResult, readInt()); + MenuResult = *IntResult; + } + + ASSIGN_OR_RETURN(FlagsResult, parseFlags(MenuDefinition::OptionsStr, + MenuDefinition::OptionsFlags)); + + if (IsPopup) { + // If POPUP, read submenu items recursively. + ASSIGN_OR_RETURN(SubMenuResult, parseMenuItemsList()); + List.addDefinition(std::make_unique<PopupItem>( + *CaptionResult, *FlagsResult, std::move(*SubMenuResult))); + continue; + } + + assert(IsMenuItem); + List.addDefinition( + std::make_unique<MenuItem>(*CaptionResult, MenuResult, *FlagsResult)); + } + + return std::move(List); +} + +RCParser::ParseType RCParser::parseStringTableResource() { + uint16_t MemoryFlags = + parseMemoryFlags(StringTableResource::getDefaultMemoryFlags()); + ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements()); + RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); + + auto Table = std::make_unique<StringTableResource>(std::move(*OptStatements), + MemoryFlags); + + // Read strings until we reach the end of the block. + while (!consumeOptionalType(Kind::BlockEnd)) { + // Each definition consists of string's ID (an integer) and a string. + // Some examples in documentation suggest that there might be a comma in + // between, however we strictly adhere to the single statement definition. + ASSIGN_OR_RETURN(IDResult, readInt()); + consumeOptionalType(Kind::Comma); + + std::vector<StringRef> Strings; + ASSIGN_OR_RETURN(StrResult, readString()); + Strings.push_back(*StrResult); + while (isNextTokenKind(Kind::String)) + Strings.push_back(read().value()); + + Table->addStrings(*IDResult, std::move(Strings)); + } + + return std::move(Table); +} + +Expected<std::unique_ptr<VersionInfoBlock>> +RCParser::parseVersionInfoBlockContents(StringRef BlockName) { + RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); + + auto Contents = std::make_unique<VersionInfoBlock>(BlockName); + + while (!isNextTokenKind(Kind::BlockEnd)) { + ASSIGN_OR_RETURN(Stmt, parseVersionInfoStmt()); + Contents->addStmt(std::move(*Stmt)); + } + + consume(); // Consume BlockEnd. + + return std::move(Contents); +} + +Expected<std::unique_ptr<VersionInfoStmt>> RCParser::parseVersionInfoStmt() { + // Expect either BLOCK or VALUE, then a name or a key (a string). + ASSIGN_OR_RETURN(TypeResult, readIdentifier()); + + if (TypeResult->equals_insensitive("BLOCK")) { + ASSIGN_OR_RETURN(NameResult, readString()); + return parseVersionInfoBlockContents(*NameResult); + } + + if (TypeResult->equals_insensitive("VALUE")) { + ASSIGN_OR_RETURN(KeyResult, readString()); + // 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<IntOrString> Values; + BitVector PrecedingCommas; + RETURN_IF_ERROR(consumeType(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 std::make_unique<VersionInfoValue>(*KeyResult, std::move(Values), + std::move(PrecedingCommas)); + } + + return getExpectedError("BLOCK or VALUE", true); +} + +Expected<VersionInfoResource::VersionInfoFixed> +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(1, NumInts)); + SmallVector<uint32_t, 4> ArgInts(ArgsResult->begin(), ArgsResult->end()); + while (ArgInts.size() < NumInts) + ArgInts.push_back(0); + Result.setValue(FixedType, ArgInts); + } + + return Result; +} + +RCParser::ParseOptionType RCParser::parseLanguageStmt() { + ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 2, /* max = */ 2)); + return std::make_unique<LanguageResource>((*Args)[0], (*Args)[1]); +} + +RCParser::ParseOptionType RCParser::parseCharacteristicsStmt() { + ASSIGN_OR_RETURN(Arg, readInt()); + return std::make_unique<CharacteristicsStmt>(*Arg); +} + +RCParser::ParseOptionType RCParser::parseVersionStmt() { + ASSIGN_OR_RETURN(Arg, readInt()); + return std::make_unique<VersionStmt>(*Arg); +} + +RCParser::ParseOptionType RCParser::parseCaptionStmt() { + ASSIGN_OR_RETURN(Arg, readString()); + return std::make_unique<CaptionStmt>(*Arg); +} + +RCParser::ParseOptionType RCParser::parseClassStmt() { + ASSIGN_OR_RETURN(Arg, readIntOrString()); + return std::make_unique<ClassStmt>(*Arg); +} + +RCParser::ParseOptionType RCParser::parseFontStmt(OptStmtType DialogType) { + assert(DialogType != OptStmtType::BasicStmt); + + ASSIGN_OR_RETURN(SizeResult, readInt()); + RETURN_IF_ERROR(consumeType(Kind::Comma)); + ASSIGN_OR_RETURN(NameResult, readString()); + + // Default values for the optional arguments. + uint32_t FontWeight = 0; + bool FontItalic = false; + uint32_t FontCharset = 1; + if (DialogType == OptStmtType::DialogExStmt) { + if (consumeOptionalType(Kind::Comma)) { + ASSIGN_OR_RETURN(Args, readIntsWithCommas(/* min = */ 0, /* max = */ 3)); + if (Args->size() >= 1) + FontWeight = (*Args)[0]; + if (Args->size() >= 2) + FontItalic = (*Args)[1] != 0; + if (Args->size() >= 3) + FontCharset = (*Args)[2]; + } + } + return std::make_unique<FontStmt>(*SizeResult, *NameResult, FontWeight, + FontItalic, FontCharset); +} + +RCParser::ParseOptionType RCParser::parseStyleStmt() { + ASSIGN_OR_RETURN(Arg, readInt()); + return std::make_unique<StyleStmt>(*Arg); +} + +RCParser::ParseOptionType RCParser::parseExStyleStmt() { + ASSIGN_OR_RETURN(Arg, readInt()); + return std::make_unique<ExStyleStmt>(*Arg); +} + +Error RCParser::getExpectedError(const Twine &Message, bool IsAlreadyRead) { + return make_error<ParserError>( + Message, IsAlreadyRead ? std::prev(CurLoc) : CurLoc, End); +} + +} // namespace rc +} // namespace llvm diff --git a/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptParser.h b/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptParser.h new file mode 100644 index 0000000000..a7d3b595f7 --- /dev/null +++ b/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptParser.h @@ -0,0 +1,189 @@ +//===-- ResourceScriptParser.h ----------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This defines the RC scripts parser. It takes a sequence of RC tokens +// and then provides the method to parse the resources one by one. +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_LLVMRC_RESOURCESCRIPTPARSER_H +#define LLVM_TOOLS_LLVMRC_RESOURCESCRIPTPARSER_H + +#include "ResourceScriptStmt.h" +#include "ResourceScriptToken.h" + +#include "llvm/Support/Compiler.h" +#include "llvm/Support/raw_ostream.h" + +#include <system_error> +#include <vector> + +namespace llvm { +namespace rc { + +class RCParser { +public: + using LocIter = std::vector<RCToken>::iterator; + using ParseType = Expected<std::unique_ptr<RCResource>>; + using ParseOptionType = Expected<std::unique_ptr<OptionalStmt>>; + + // Class describing a single failure of parser. + class ParserError : public ErrorInfo<ParserError> { + public: + ParserError(const Twine &Expected, const LocIter CurLoc, const LocIter End); + + void log(raw_ostream &OS) const override { OS << CurMessage; } + std::error_code convertToErrorCode() const override { + return std::make_error_code(std::errc::invalid_argument); + } + const std::string &getMessage() const { return CurMessage; } + + static char ID; // Keep llvm::Error happy. + + private: + std::string CurMessage; + LocIter ErrorLoc, FileEnd; + }; + + explicit RCParser(std::vector<RCToken> TokenList); + + // Reads and returns a single resource definition, or error message if any + // occurred. + ParseType parseSingleResource(); + + bool isEof() const; + +private: + using Kind = RCToken::Kind; + + // Checks if the current parser state points to the token of type TokenKind. + bool isNextTokenKind(Kind TokenKind) const; + + // These methods assume that the parser is not in EOF state. + + // Take a look at the current token. Do not fetch it. + const RCToken &look() const; + // Read the current token and advance the state by one token. + const RCToken &read(); + // Advance the state by one token, discarding the current token. + void consume(); + + // The following methods try to read a single token, check if it has the + // correct type and then parse it. + // Each integer can be written as an arithmetic expression producing an + // unsigned 32-bit integer. + Expected<RCInt> readInt(); // Parse an integer. + Expected<StringRef> readString(); // Parse a string. + Expected<StringRef> readIdentifier(); // Parse an identifier. + Expected<StringRef> readFilename(); // Parse a filename. + Expected<IntOrString> readIntOrString(); // Parse an integer or a string. + Expected<IntOrString> readTypeOrName(); // Parse an integer or an identifier. + + // Helper integer expression parsing methods. + Expected<IntWithNotMask> parseIntExpr1(); + Expected<IntWithNotMask> parseIntExpr2(); + + // Advance the state by one, discarding the current token. + // If the discarded token had an incorrect type, fail. + Error consumeType(Kind TokenKind); + + // Check the current token type. If it's TokenKind, discard it. + // Return true if the parser consumed this token successfully. + bool consumeOptionalType(Kind TokenKind); + + // Read at least MinCount, and at most MaxCount integers separated by + // 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<SmallVector<RCInt, 8>> 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 + // flag (0-based) has been read, the result is OR-ed with FlagValues[i]. + // As long as parser has a comma to read, it expects to be fed with + // a correct flag afterwards. + Expected<uint32_t> parseFlags(ArrayRef<StringRef> FlagDesc, + ArrayRef<uint32_t> FlagValues); + + // 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 + // before the main block with the contents of the resource. + // Usually, resources use a basic set of optional statements: + // CHARACTERISTICS, LANGUAGE, VERSION + // However, DIALOG and DIALOGEX extend this list by the following items: + // CAPTION, CLASS, EXSTYLE, FONT, MENU, STYLE + // UseExtendedStatements flag (off by default) allows the parser to read + // the additional types of statements. + // + // Ref (to the list of all optional statements): + // msdn.microsoft.com/en-us/library/windows/desktop/aa381002(v=vs.85).aspx + enum class OptStmtType { BasicStmt, DialogStmt, DialogExStmt }; + + uint16_t parseMemoryFlags(uint16_t DefaultFlags); + + Expected<OptionalStmtList> + parseOptionalStatements(OptStmtType StmtsType = OptStmtType::BasicStmt); + + // Read a single optional statement. + Expected<std::unique_ptr<OptionalStmt>> + parseSingleOptionalStatement(OptStmtType StmtsType = OptStmtType::BasicStmt); + + // Top-level resource parsers. + ParseType parseLanguageResource(); + ParseType parseAcceleratorsResource(); + ParseType parseBitmapResource(); + ParseType parseCursorResource(); + ParseType parseDialogResource(bool IsExtended); + ParseType parseIconResource(); + ParseType parseHTMLResource(); + ParseType parseMenuResource(); + ParseType parseStringTableResource(); + ParseType parseUserDefinedResource(IntOrString Type); + ParseType parseVersionInfoResource(); + + // Helper DIALOG parser - a single control. + Expected<Control> parseControl(); + + // Helper MENU parser. + Expected<MenuDefinitionList> parseMenuItemsList(); + + // Helper VERSIONINFO parser - read the contents of a single BLOCK statement, + // from BEGIN to END. + Expected<std::unique_ptr<VersionInfoBlock>> + parseVersionInfoBlockContents(StringRef BlockName); + // Helper VERSIONINFO parser - read either VALUE or BLOCK statement. + Expected<std::unique_ptr<VersionInfoStmt>> parseVersionInfoStmt(); + // Helper VERSIONINFO parser - read fixed VERSIONINFO statements. + Expected<VersionInfoResource::VersionInfoFixed> parseVersionInfoFixed(); + + // Optional statement parsers. + ParseOptionType parseLanguageStmt(); + ParseOptionType parseCharacteristicsStmt(); + ParseOptionType parseVersionStmt(); + ParseOptionType parseCaptionStmt(); + ParseOptionType parseClassStmt(); + ParseOptionType parseExStyleStmt(); + ParseOptionType parseFontStmt(OptStmtType DialogType); + ParseOptionType parseStyleStmt(); + + // Raises an error. If IsAlreadyRead = false (default), this complains about + // the token that couldn't be parsed. If the flag is on, this complains about + // the correctly read token that makes no sense (that is, the current parser + // state is beyond the erroneous token.) + Error getExpectedError(const Twine &Message, bool IsAlreadyRead = false); + + std::vector<RCToken> Tokens; + LocIter CurLoc; + const LocIter End; +}; + +} // namespace rc +} // namespace llvm + +#endif diff --git a/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptStmt.cpp b/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptStmt.cpp new file mode 100644 index 0000000000..ef8c345418 --- /dev/null +++ b/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptStmt.cpp @@ -0,0 +1,294 @@ +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This implements methods defined in ResourceScriptStmt.h. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380599(v=vs.85).aspx +// +//===---------------------------------------------------------------------===// + +#include "ResourceScriptStmt.h" + +namespace llvm { +namespace rc { + +raw_ostream &operator<<(raw_ostream &OS, const IntOrString &Item) { + if (Item.IsInt) + return OS << Item.Data.Int; + else + return OS << Item.Data.String; +} + +raw_ostream &OptionalStmtList::log(raw_ostream &OS) const { + for (const auto &Stmt : Statements) { + OS << " Option: "; + Stmt->log(OS); + } + return OS; +} + +raw_ostream &LanguageResource::log(raw_ostream &OS) const { + return OS << "Language: " << Lang << ", Sublanguage: " << SubLang << "\n"; +} + +StringRef AcceleratorsResource::Accelerator::OptionsStr + [AcceleratorsResource::Accelerator::NumFlags] = { + "ASCII", "VIRTKEY", "NOINVERT", "ALT", "SHIFT", "CONTROL"}; + +uint32_t AcceleratorsResource::Accelerator::OptionsFlags + [AcceleratorsResource::Accelerator::NumFlags] = {ASCII, VIRTKEY, NOINVERT, + ALT, SHIFT, CONTROL}; + +raw_ostream &AcceleratorsResource::log(raw_ostream &OS) const { + OS << "Accelerators (" << ResName << "): \n"; + OptStatements->log(OS); + for (const auto &Acc : Accelerators) { + OS << " Accelerator: " << Acc.Event << " " << Acc.Id; + for (size_t i = 0; i < Accelerator::NumFlags; ++i) + if (Acc.Flags & Accelerator::OptionsFlags[i]) + OS << " " << Accelerator::OptionsStr[i]; + OS << "\n"; + } + return OS; +} + +raw_ostream &BitmapResource::log(raw_ostream &OS) const { + return OS << "Bitmap (" << ResName << "): " << BitmapLoc << "\n"; +} + +raw_ostream &CursorResource::log(raw_ostream &OS) const { + return OS << "Cursor (" << ResName << "): " << CursorLoc << "\n"; +} + +raw_ostream &IconResource::log(raw_ostream &OS) const { + return OS << "Icon (" << ResName << "): " << IconLoc << "\n"; +} + +raw_ostream &HTMLResource::log(raw_ostream &OS) const { + return OS << "HTML (" << ResName << "): " << HTMLLoc << "\n"; +} + +StringRef MenuDefinition::OptionsStr[MenuDefinition::NumFlags] = { + "CHECKED", "GRAYED", "HELP", "INACTIVE", "MENUBARBREAK", "MENUBREAK"}; + +uint32_t MenuDefinition::OptionsFlags[MenuDefinition::NumFlags] = { + CHECKED, GRAYED, HELP, INACTIVE, MENUBARBREAK, MENUBREAK}; + +raw_ostream &MenuDefinition::logFlags(raw_ostream &OS, uint16_t Flags) { + for (size_t i = 0; i < NumFlags; ++i) + if (Flags & OptionsFlags[i]) + OS << " " << OptionsStr[i]; + return OS; +} + +raw_ostream &MenuDefinitionList::log(raw_ostream &OS) const { + OS << " Menu list starts\n"; + for (auto &Item : Definitions) + Item->log(OS); + return OS << " Menu list ends\n"; +} + +raw_ostream &MenuItem::log(raw_ostream &OS) const { + OS << " MenuItem (" << Name << "), ID = " << Id; + logFlags(OS, Flags); + return OS << "\n"; +} + +raw_ostream &MenuSeparator::log(raw_ostream &OS) const { + return OS << " Menu separator\n"; +} + +raw_ostream &PopupItem::log(raw_ostream &OS) const { + OS << " Popup (" << Name << ")"; + logFlags(OS, Flags); + OS << ":\n"; + return SubItems.log(OS); +} + +raw_ostream &MenuResource::log(raw_ostream &OS) const { + OS << "Menu (" << ResName << "):\n"; + OptStatements->log(OS); + return Elements.log(OS); +} + +raw_ostream &StringTableResource::log(raw_ostream &OS) const { + OS << "StringTable:\n"; + OptStatements->log(OS); + for (const auto &String : Table) { + OS << " " << String.first << " =>"; + for (const auto &S : String.second) + OS << " " << S; + OS << "\n"; + } + return OS; +} + +const StringMap<Control::CtlInfo> Control::SupportedCtls = { + {"LTEXT", CtlInfo{0x50020000, ClsStatic, true}}, + {"CTEXT", CtlInfo{0x50020001, ClsStatic, true}}, + {"RTEXT", CtlInfo{0x50020002, ClsStatic, true}}, + {"ICON", CtlInfo{0x50000003, ClsStatic, true}}, + {"PUSHBUTTON", CtlInfo{0x50010000, ClsButton, true}}, + {"DEFPUSHBUTTON", CtlInfo{0x50010001, ClsButton, true}}, + {"AUTO3STATE", CtlInfo{0x50010006, ClsButton, true}}, + {"AUTOCHECKBOX", CtlInfo{0x50010003, ClsButton, true}}, + {"AUTORADIOBUTTON", CtlInfo{0x50000009, ClsButton, true}}, + {"CHECKBOX", CtlInfo{0x50010002, ClsButton, true}}, + {"GROUPBOX", CtlInfo{0x50000007, ClsButton, true}}, + {"RADIOBUTTON", CtlInfo{0x50000004, ClsButton, true}}, + {"STATE3", CtlInfo{0x50010005, ClsButton, true}}, + {"PUSHBOX", CtlInfo{0x5001000A, ClsButton, true}}, + {"EDITTEXT", CtlInfo{0x50810000, ClsEdit, false}}, + {"COMBOBOX", CtlInfo{0x50000000, ClsComboBox, false}}, + {"LISTBOX", CtlInfo{0x50800001, ClsListBox, false}}, + {"SCROLLBAR", CtlInfo{0x50000000, ClsScrollBar, false}}, + {"CONTROL", CtlInfo{0x50000000, 0, true}}, +}; + +raw_ostream &Control::log(raw_ostream &OS) const { + OS << " Control (" << ID << "): " << Type << ", title: " << Title + << ", loc: (" << X << ", " << Y << "), size: [" << Width << ", " << Height + << "]"; + if (Style) + OS << ", style: " << (*Style).getValue(); + if (ExtStyle) + OS << ", ext. style: " << *ExtStyle; + if (HelpID) + OS << ", help ID: " << *HelpID; + return OS << "\n"; +} + +raw_ostream &DialogResource::log(raw_ostream &OS) const { + OS << "Dialog" << (IsExtended ? "Ex" : "") << " (" << ResName << "): loc: (" + << X << ", " << Y << "), size: [" << Width << ", " << Height + << "], help ID: " << HelpID << "\n"; + OptStatements->log(OS); + for (auto &Ctl : Controls) + Ctl.log(OS); + 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 << " =>"; + 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"; +} + +using VersionInfoFixed = VersionInfoResource::VersionInfoFixed; +using VersionInfoFixedType = VersionInfoFixed::VersionInfoFixedType; + +const StringRef + VersionInfoFixed::FixedFieldsNames[VersionInfoFixed::FtNumTypes] = { + "", "FILEVERSION", "PRODUCTVERSION", "FILEFLAGSMASK", + "FILEFLAGS", "FILEOS", "FILETYPE", "FILESUBTYPE"}; + +const StringMap<VersionInfoFixedType> 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 &UserDefinedResource::log(raw_ostream &OS) const { + OS << "User-defined (type: " << Type << ", name: " << ResName << "): "; + if (IsFileResource) + return OS << FileLoc << "\n"; + OS << "data = "; + for (auto &Item : Contents) + OS << Item << " "; + return OS << "\n"; +} + +raw_ostream &CharacteristicsStmt::log(raw_ostream &OS) const { + return OS << "Characteristics: " << Value << "\n"; +} + +raw_ostream &VersionStmt::log(raw_ostream &OS) const { + return OS << "Version: " << Value << "\n"; +} + +raw_ostream &CaptionStmt::log(raw_ostream &OS) const { + return OS << "Caption: " << Value << "\n"; +} + +raw_ostream &ClassStmt::log(raw_ostream &OS) const { + return OS << "Class: " << Value << "\n"; +} + +raw_ostream &FontStmt::log(raw_ostream &OS) const { + OS << "Font: size = " << Size << ", face = " << Name + << ", weight = " << Weight; + if (Italic) + OS << ", italic"; + return OS << ", charset = " << Charset << "\n"; +} + +raw_ostream &StyleStmt::log(raw_ostream &OS) const { + return OS << "Style: " << Value << "\n"; +} + +raw_ostream &ExStyleStmt::log(raw_ostream &OS) const { + return OS << "ExStyle: " << Value << "\n"; +} + +} // namespace rc +} // namespace llvm diff --git a/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptStmt.h b/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptStmt.h new file mode 100644 index 0000000000..4b659f083b --- /dev/null +++ b/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptStmt.h @@ -0,0 +1,954 @@ +//===-- ResourceScriptStmt.h ------------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This lists all the resource and statement types occurring in RC scripts. +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_LLVMRC_RESOURCESCRIPTSTMT_H +#define LLVM_TOOLS_LLVMRC_RESOURCESCRIPTSTMT_H + +#include "ResourceScriptToken.h" +#include "ResourceVisitor.h" + +#include "llvm/ADT/BitVector.h" +#include "llvm/ADT/StringSet.h" + +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" : ""); + } +}; + +class IntWithNotMask { +private: + RCInt Value; + int32_t NotMask; + +public: + IntWithNotMask() : IntWithNotMask(RCInt(0)) {} + IntWithNotMask(RCInt Value, int32_t NotMask = 0) : Value(Value), NotMask(NotMask) {} + + RCInt getValue() const { + return Value; + } + + uint32_t getNotMask() const { + return NotMask; + } + + IntWithNotMask &operator+=(const IntWithNotMask &Rhs) { + Value &= ~Rhs.NotMask; + Value += Rhs.Value; + NotMask |= Rhs.NotMask; + return *this; + } + + IntWithNotMask &operator-=(const IntWithNotMask &Rhs) { + Value &= ~Rhs.NotMask; + Value -= Rhs.Value; + NotMask |= Rhs.NotMask; + return *this; + } + + IntWithNotMask &operator|=(const IntWithNotMask &Rhs) { + Value &= ~Rhs.NotMask; + Value |= Rhs.Value; + NotMask |= Rhs.NotMask; + return *this; + } + + IntWithNotMask &operator&=(const IntWithNotMask &Rhs) { + Value &= ~Rhs.NotMask; + Value &= Rhs.Value; + NotMask |= Rhs.NotMask; + return *this; + } + + IntWithNotMask operator-() const { return {-Value, NotMask}; } + IntWithNotMask operator~() const { return {~Value, 0}; } + + friend raw_ostream &operator<<(raw_ostream &OS, const IntWithNotMask &Int) { + return OS << Int.Value; + } +}; + +// A class holding a name - either an integer or a reference to the string. +class IntOrString { +private: + union Data { + RCInt Int; + StringRef String; + Data(RCInt Value) : Int(Value) {} + Data(const StringRef Value) : String(Value) {} + Data(const RCToken &Token) { + if (Token.kind() == RCToken::Kind::Int) + Int = RCInt(Token); + else + String = Token.value(); + } + } Data; + bool IsInt; + +public: + IntOrString() : IntOrString(RCInt(0)) {} + IntOrString(uint32_t Value) : Data(Value), IsInt(true) {} + IntOrString(RCInt Value) : Data(Value), IsInt(true) {} + IntOrString(StringRef Value) : Data(Value), IsInt(false) {} + IntOrString(const RCToken &Token) + : Data(Token), IsInt(Token.kind() == RCToken::Kind::Int) {} + + bool equalsLower(const char *Str) { + return !IsInt && Data.String.equals_insensitive(Str); + } + + bool isInt() const { return IsInt; } + + RCInt getInt() const { + assert(IsInt); + return Data.Int; + } + + const StringRef &getString() const { + assert(!IsInt); + return Data.String; + } + + operator Twine() const { + return isInt() ? Twine(getInt()) : Twine(getString()); + } + + friend raw_ostream &operator<<(raw_ostream &, const IntOrString &); +}; + +enum ResourceKind { + // These resource kinds have corresponding .res resource type IDs + // (TYPE in RESOURCEHEADER structure). The numeric value assigned to each + // kind is equal to this type ID. + RkNull = 0, + RkSingleCursor = 1, + RkBitmap = 2, + RkSingleIcon = 3, + RkMenu = 4, + RkDialog = 5, + RkStringTableBundle = 6, + RkAccelerators = 9, + RkRcData = 10, + RkCursorGroup = 12, + RkIconGroup = 14, + RkVersionInfo = 16, + RkHTML = 23, + + // These kinds don't have assigned type IDs (they might be the resources + // of invalid kind, expand to many resource structures in .res files, + // or have variable type ID). In order to avoid ID clashes with IDs above, + // we assign the kinds the values 256 and larger. + RkInvalid = 256, + RkBase, + RkCursor, + RkIcon, + RkStringTable, + RkUser, + RkSingleCursorOrIconRes, + RkCursorOrIconGroupRes, +}; + +// Non-zero memory flags. +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648027(v=vs.85).aspx +enum MemoryFlags { + MfMoveable = 0x10, + MfPure = 0x20, + MfPreload = 0x40, + MfDiscardable = 0x1000 +}; + +// Base resource. All the resources should derive from this base. +class RCResource { +public: + IntOrString ResName; + uint16_t MemoryFlags = getDefaultMemoryFlags(); + void setName(const IntOrString &Name) { ResName = Name; } + virtual raw_ostream &log(raw_ostream &OS) const { + return OS << "Base statement\n"; + }; + RCResource() {} + RCResource(uint16_t Flags) : MemoryFlags(Flags) {} + virtual ~RCResource() {} + + virtual Error visit(Visitor *) const { + llvm_unreachable("This is unable to call methods from Visitor base"); + } + + // Apply the statements attached to this resource. Generic resources + // don't have any. + virtual Error applyStmts(Visitor *) const { return Error::success(); } + + // By default, memory flags are DISCARDABLE | PURE | MOVEABLE. + static uint16_t getDefaultMemoryFlags() { + return MfDiscardable | MfPure | MfMoveable; + } + + virtual ResourceKind getKind() const { return RkBase; } + static bool classof(const RCResource *Res) { return true; } + + virtual IntOrString getResourceType() const { + llvm_unreachable("This cannot be called on objects without types."); + } + virtual Twine getResourceTypeName() const { + llvm_unreachable("This cannot be called on objects without types."); + }; +}; + +// An empty resource. It has no content, type 0, ID 0 and all of its +// characteristics are equal to 0. +class NullResource : public RCResource { +public: + NullResource() : RCResource(0) {} + raw_ostream &log(raw_ostream &OS) const override { + return OS << "Null resource\n"; + } + Error visit(Visitor *V) const override { return V->visitNullResource(this); } + IntOrString getResourceType() const override { return 0; } + Twine getResourceTypeName() const override { return "(NULL)"; } +}; + +// Optional statement base. All such statements should derive from this base. +class OptionalStmt : public RCResource {}; + +class OptionalStmtList : public OptionalStmt { + std::vector<std::unique_ptr<OptionalStmt>> Statements; + +public: + OptionalStmtList() {} + raw_ostream &log(raw_ostream &OS) const override; + + void addStmt(std::unique_ptr<OptionalStmt> Stmt) { + Statements.push_back(std::move(Stmt)); + } + + Error visit(Visitor *V) const override { + for (auto &StmtPtr : Statements) + if (auto Err = StmtPtr->visit(V)) + return Err; + return Error::success(); + } +}; + +class OptStatementsRCResource : public RCResource { +public: + std::unique_ptr<OptionalStmtList> OptStatements; + + OptStatementsRCResource(OptionalStmtList &&Stmts, + uint16_t Flags = RCResource::getDefaultMemoryFlags()) + : RCResource(Flags), + OptStatements(std::make_unique<OptionalStmtList>(std::move(Stmts))) {} + + Error applyStmts(Visitor *V) const override { + return OptStatements->visit(V); + } +}; + +// LANGUAGE statement. It can occur both as a top-level statement (in such +// a situation, it changes the default language until the end of the file) +// and as an optional resource statement (then it changes the language +// of a single resource). +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381019(v=vs.85).aspx +class LanguageResource : public OptionalStmt { +public: + uint32_t Lang, SubLang; + + LanguageResource(uint32_t LangId, uint32_t SubLangId) + : Lang(LangId), SubLang(SubLangId) {} + raw_ostream &log(raw_ostream &) const override; + + // This is not a regular top-level statement; when it occurs, it just + // modifies the language context. + Error visit(Visitor *V) const override { return V->visitLanguageStmt(this); } + Twine getResourceTypeName() const override { return "LANGUAGE"; } +}; + +// ACCELERATORS resource. Defines a named table of accelerators for the app. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380610(v=vs.85).aspx +class AcceleratorsResource : public OptStatementsRCResource { +public: + class Accelerator { + public: + IntOrString Event; + uint32_t Id; + uint16_t Flags; + + enum Options { + // This is actually 0x0000 (accelerator is assumed to be ASCII if it's + // not VIRTKEY). However, rc.exe behavior is different in situations + // "only ASCII defined" and "neither ASCII nor VIRTKEY defined". + // Therefore, we include ASCII as another flag. This must be zeroed + // when serialized. + ASCII = 0x8000, + VIRTKEY = 0x0001, + NOINVERT = 0x0002, + ALT = 0x0010, + SHIFT = 0x0004, + CONTROL = 0x0008 + }; + + static constexpr size_t NumFlags = 6; + static StringRef OptionsStr[NumFlags]; + static uint32_t OptionsFlags[NumFlags]; + }; + + AcceleratorsResource(OptionalStmtList &&List, uint16_t Flags) + : OptStatementsRCResource(std::move(List), Flags) {} + + std::vector<Accelerator> Accelerators; + + void addAccelerator(IntOrString Event, uint32_t Id, uint16_t Flags) { + Accelerators.push_back(Accelerator{Event, Id, Flags}); + } + raw_ostream &log(raw_ostream &) const override; + + IntOrString getResourceType() const override { return RkAccelerators; } + static uint16_t getDefaultMemoryFlags() { return MfPure | MfMoveable; } + Twine getResourceTypeName() const override { return "ACCELERATORS"; } + + Error visit(Visitor *V) const override { + return V->visitAcceleratorsResource(this); + } + ResourceKind getKind() const override { return RkAccelerators; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkAccelerators; + } +}; + +// BITMAP resource. Represents a bitmap (".bmp") file. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380680(v=vs.85).aspx +class BitmapResource : public RCResource { +public: + StringRef BitmapLoc; + + BitmapResource(StringRef Location, uint16_t Flags) + : RCResource(Flags), BitmapLoc(Location) {} + raw_ostream &log(raw_ostream &) const override; + + IntOrString getResourceType() const override { return RkBitmap; } + static uint16_t getDefaultMemoryFlags() { return MfPure | MfMoveable; } + + Twine getResourceTypeName() const override { return "BITMAP"; } + Error visit(Visitor *V) const override { + return V->visitBitmapResource(this); + } + ResourceKind getKind() const override { return RkBitmap; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkBitmap; + } +}; + +// CURSOR resource. Represents a single cursor (".cur") file. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380920(v=vs.85).aspx +class CursorResource : public RCResource { +public: + StringRef CursorLoc; + + CursorResource(StringRef Location, uint16_t Flags) + : RCResource(Flags), CursorLoc(Location) {} + raw_ostream &log(raw_ostream &) const override; + + Twine getResourceTypeName() const override { return "CURSOR"; } + static uint16_t getDefaultMemoryFlags() { return MfDiscardable | MfMoveable; } + Error visit(Visitor *V) const override { + return V->visitCursorResource(this); + } + ResourceKind getKind() const override { return RkCursor; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkCursor; + } +}; + +// ICON resource. Represents a single ".ico" file containing a group of icons. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381018(v=vs.85).aspx +class IconResource : public RCResource { +public: + StringRef IconLoc; + + IconResource(StringRef Location, uint16_t Flags) + : RCResource(Flags), IconLoc(Location) {} + raw_ostream &log(raw_ostream &) const override; + + Twine getResourceTypeName() const override { return "ICON"; } + static uint16_t getDefaultMemoryFlags() { return MfDiscardable | MfMoveable; } + Error visit(Visitor *V) const override { return V->visitIconResource(this); } + ResourceKind getKind() const override { return RkIcon; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkIcon; + } +}; + +// HTML resource. Represents a local webpage that is to be embedded into the +// resulting resource file. It embeds a file only - no additional resources +// (images etc.) are included with this resource. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa966018(v=vs.85).aspx +class HTMLResource : public RCResource { +public: + StringRef HTMLLoc; + + HTMLResource(StringRef Location, uint16_t Flags) + : RCResource(Flags), HTMLLoc(Location) {} + raw_ostream &log(raw_ostream &) const override; + + Error visit(Visitor *V) const override { return V->visitHTMLResource(this); } + + // Curiously, file resources don't have DISCARDABLE flag set. + static uint16_t getDefaultMemoryFlags() { return MfPure | MfMoveable; } + IntOrString getResourceType() const override { return RkHTML; } + Twine getResourceTypeName() const override { return "HTML"; } + ResourceKind getKind() const override { return RkHTML; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkHTML; + } +}; + +// -- MENU resource and its helper classes -- +// This resource describes the contents of an application menu +// (usually located in the upper part of the dialog.) +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381025(v=vs.85).aspx + +// Description of a single submenu item. +class MenuDefinition { +public: + enum Options { + CHECKED = 0x0008, + GRAYED = 0x0001, + HELP = 0x4000, + INACTIVE = 0x0002, + MENUBARBREAK = 0x0020, + MENUBREAK = 0x0040 + }; + + enum MenuDefKind { MkBase, MkSeparator, MkMenuItem, MkPopup }; + + static constexpr size_t NumFlags = 6; + static StringRef OptionsStr[NumFlags]; + static uint32_t OptionsFlags[NumFlags]; + static raw_ostream &logFlags(raw_ostream &, uint16_t Flags); + virtual raw_ostream &log(raw_ostream &OS) const { + return OS << "Base menu definition\n"; + } + virtual ~MenuDefinition() {} + + virtual uint16_t getResFlags() const { return 0; } + virtual MenuDefKind getKind() const { return MkBase; } +}; + +// Recursive description of a whole submenu. +class MenuDefinitionList : public MenuDefinition { +public: + std::vector<std::unique_ptr<MenuDefinition>> Definitions; + + void addDefinition(std::unique_ptr<MenuDefinition> Def) { + Definitions.push_back(std::move(Def)); + } + raw_ostream &log(raw_ostream &) const override; +}; + +// Separator in MENU definition (MENUITEM SEPARATOR). +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381024(v=vs.85).aspx +class MenuSeparator : public MenuDefinition { +public: + raw_ostream &log(raw_ostream &) const override; + + MenuDefKind getKind() const override { return MkSeparator; } + static bool classof(const MenuDefinition *D) { + return D->getKind() == MkSeparator; + } +}; + +// MENUITEM statement definition. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381024(v=vs.85).aspx +class MenuItem : public MenuDefinition { +public: + StringRef Name; + uint32_t Id; + uint16_t Flags; + + MenuItem(StringRef Caption, uint32_t ItemId, uint16_t ItemFlags) + : Name(Caption), Id(ItemId), Flags(ItemFlags) {} + raw_ostream &log(raw_ostream &) const override; + + uint16_t getResFlags() const override { return Flags; } + MenuDefKind getKind() const override { return MkMenuItem; } + static bool classof(const MenuDefinition *D) { + return D->getKind() == MkMenuItem; + } +}; + +// POPUP statement definition. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381030(v=vs.85).aspx +class PopupItem : public MenuDefinition { +public: + StringRef Name; + uint16_t Flags; + MenuDefinitionList SubItems; + + PopupItem(StringRef Caption, uint16_t ItemFlags, + MenuDefinitionList &&SubItemsList) + : Name(Caption), Flags(ItemFlags), SubItems(std::move(SubItemsList)) {} + raw_ostream &log(raw_ostream &) const override; + + // This has an additional (0x10) flag. It doesn't match with documented + // 0x01 flag, though. + uint16_t getResFlags() const override { return Flags | 0x10; } + MenuDefKind getKind() const override { return MkPopup; } + static bool classof(const MenuDefinition *D) { + return D->getKind() == MkPopup; + } +}; + +// Menu resource definition. +class MenuResource : public OptStatementsRCResource { +public: + MenuDefinitionList Elements; + + MenuResource(OptionalStmtList &&OptStmts, MenuDefinitionList &&Items, + uint16_t Flags) + : OptStatementsRCResource(std::move(OptStmts), Flags), + Elements(std::move(Items)) {} + raw_ostream &log(raw_ostream &) const override; + + IntOrString getResourceType() const override { return RkMenu; } + Twine getResourceTypeName() const override { return "MENU"; } + Error visit(Visitor *V) const override { return V->visitMenuResource(this); } + ResourceKind getKind() const override { return RkMenu; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkMenu; + } +}; + +// STRINGTABLE resource. Contains a list of strings, each having its unique ID. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381050(v=vs.85).aspx +class StringTableResource : public OptStatementsRCResource { +public: + std::vector<std::pair<uint32_t, std::vector<StringRef>>> Table; + + StringTableResource(OptionalStmtList &&List, uint16_t Flags) + : OptStatementsRCResource(std::move(List), Flags) {} + void addStrings(uint32_t ID, std::vector<StringRef> &&Strings) { + Table.emplace_back(ID, Strings); + } + raw_ostream &log(raw_ostream &) const override; + Twine getResourceTypeName() const override { return "STRINGTABLE"; } + Error visit(Visitor *V) const override { + return V->visitStringTableResource(this); + } +}; + +// -- DIALOG(EX) resource and its helper classes -- +// +// This resource describes dialog boxes and controls residing inside them. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381003(v=vs.85).aspx +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381002(v=vs.85).aspx + +// Single control definition. +class Control { +public: + StringRef Type; + IntOrString Title; + uint32_t ID, X, Y, Width, Height; + Optional<IntWithNotMask> Style; + Optional<uint32_t> ExtStyle, HelpID; + IntOrString Class; + + // Control classes as described in DLGITEMTEMPLATEEX documentation. + // + // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms645389.aspx + enum CtlClasses { + ClsButton = 0x80, + ClsEdit = 0x81, + ClsStatic = 0x82, + ClsListBox = 0x83, + ClsScrollBar = 0x84, + ClsComboBox = 0x85 + }; + + // Simple information about a single control type. + struct CtlInfo { + uint32_t Style; + uint16_t CtlClass; + bool HasTitle; + }; + + Control(StringRef CtlType, IntOrString CtlTitle, uint32_t CtlID, + uint32_t PosX, uint32_t PosY, uint32_t ItemWidth, uint32_t ItemHeight, + Optional<IntWithNotMask> ItemStyle, Optional<uint32_t> ExtItemStyle, + Optional<uint32_t> CtlHelpID, IntOrString CtlClass) + : Type(CtlType), Title(CtlTitle), ID(CtlID), X(PosX), Y(PosY), + Width(ItemWidth), Height(ItemHeight), Style(ItemStyle), + ExtStyle(ExtItemStyle), HelpID(CtlHelpID), Class(CtlClass) {} + + static const StringMap<CtlInfo> SupportedCtls; + + raw_ostream &log(raw_ostream &) const; +}; + +// Single dialog definition. We don't create distinct classes for DIALOG and +// DIALOGEX because of their being too similar to each other. We only have a +// flag determining the type of the dialog box. +class DialogResource : public OptStatementsRCResource { +public: + uint32_t X, Y, Width, Height, HelpID; + std::vector<Control> Controls; + bool IsExtended; + + DialogResource(uint32_t PosX, uint32_t PosY, uint32_t DlgWidth, + uint32_t DlgHeight, uint32_t DlgHelpID, + OptionalStmtList &&OptStmts, bool IsDialogEx, uint16_t Flags) + : OptStatementsRCResource(std::move(OptStmts), Flags), X(PosX), Y(PosY), + Width(DlgWidth), Height(DlgHeight), HelpID(DlgHelpID), + IsExtended(IsDialogEx) {} + + void addControl(Control &&Ctl) { Controls.push_back(std::move(Ctl)); } + + raw_ostream &log(raw_ostream &) const override; + + // It was a weird design decision to assign the same resource type number + // both for DIALOG and DIALOGEX (and the same structure version number). + // It makes it possible for DIALOG to be mistaken for DIALOGEX. + IntOrString getResourceType() const override { return RkDialog; } + Twine getResourceTypeName() const override { + return "DIALOG" + Twine(IsExtended ? "EX" : ""); + } + Error visit(Visitor *V) const override { + return V->visitDialogResource(this); + } + ResourceKind getKind() const override { return RkDialog; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkDialog; + } +}; + +// User-defined resource. It is either: +// * a link to the file, e.g. NAME TYPE "filename", +// * or contains a list of integers and strings, e.g. NAME TYPE {1, "a", 2}. +class UserDefinedResource : public RCResource { +public: + IntOrString Type; + StringRef FileLoc; + std::vector<IntOrString> Contents; + bool IsFileResource; + + UserDefinedResource(IntOrString ResourceType, StringRef FileLocation, + uint16_t Flags) + : RCResource(Flags), Type(ResourceType), FileLoc(FileLocation), + IsFileResource(true) {} + UserDefinedResource(IntOrString ResourceType, std::vector<IntOrString> &&Data, + uint16_t Flags) + : RCResource(Flags), Type(ResourceType), Contents(std::move(Data)), + IsFileResource(false) {} + + raw_ostream &log(raw_ostream &) const override; + IntOrString getResourceType() const override { return Type; } + Twine getResourceTypeName() const override { return Type; } + static uint16_t getDefaultMemoryFlags() { return MfPure | MfMoveable; } + + Error visit(Visitor *V) const override { + return V->visitUserDefinedResource(this); + } + ResourceKind getKind() const override { return RkUser; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkUser; + } +}; + +// -- 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: + 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 +// 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 { +public: + std::vector<std::unique_ptr<VersionInfoStmt>> Stmts; + StringRef Name; + + VersionInfoBlock(StringRef BlockName) : Name(BlockName) {} + void addStmt(std::unique_ptr<VersionInfoStmt> 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<IntOrString> Values; + BitVector HasPrecedingComma; + + VersionInfoValue(StringRef InfoKey, std::vector<IntOrString> &&Vals, + BitVector &&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 { +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<VersionInfoFixedType> FixedFieldsInfoMap; + static const StringRef FixedFieldsNames[FtNumTypes]; + + public: + SmallVector<uint32_t, 4> FixedInfo[FtNumTypes]; + SmallVector<bool, FtNumTypes> 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<uint32_t> Value) { + FixedInfo[Type] = SmallVector<uint32_t, 4>(Value.begin(), Value.end()); + IsTypePresent[Type] = true; + } + + raw_ostream &log(raw_ostream &) const; + }; + + VersionInfoBlock MainBlock; + VersionInfoFixed FixedData; + + VersionInfoResource(VersionInfoBlock &&TopLevelBlock, + VersionInfoFixed &&FixedInfo, uint16_t Flags) + : RCResource(Flags), MainBlock(std::move(TopLevelBlock)), + FixedData(std::move(FixedInfo)) {} + + raw_ostream &log(raw_ostream &) const override; + IntOrString getResourceType() const override { return RkVersionInfo; } + static uint16_t getDefaultMemoryFlags() { 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. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380872(v=vs.85).aspx +class CharacteristicsStmt : public OptionalStmt { +public: + uint32_t Value; + + CharacteristicsStmt(uint32_t Characteristic) : Value(Characteristic) {} + raw_ostream &log(raw_ostream &) const override; + + Twine getResourceTypeName() const override { return "CHARACTERISTICS"; } + Error visit(Visitor *V) const override { + return V->visitCharacteristicsStmt(this); + } +}; + +// VERSION optional statement. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381059(v=vs.85).aspx +class VersionStmt : public OptionalStmt { +public: + uint32_t Value; + + VersionStmt(uint32_t Version) : Value(Version) {} + raw_ostream &log(raw_ostream &) const override; + + Twine getResourceTypeName() const override { return "VERSION"; } + Error visit(Visitor *V) const override { return V->visitVersionStmt(this); } +}; + +// CAPTION optional statement. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380778(v=vs.85).aspx +class CaptionStmt : public OptionalStmt { +public: + StringRef Value; + + CaptionStmt(StringRef Caption) : Value(Caption) {} + raw_ostream &log(raw_ostream &) const override; + Twine getResourceTypeName() const override { return "CAPTION"; } + Error visit(Visitor *V) const override { return V->visitCaptionStmt(this); } +}; + +// FONT optional statement. +// Note that the documentation is inaccurate: it expects five arguments to be +// given, however the example provides only two. In fact, the original tool +// expects two arguments - point size and name of the typeface. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381013(v=vs.85).aspx +class FontStmt : public OptionalStmt { +public: + uint32_t Size, Weight, Charset; + StringRef Name; + bool Italic; + + FontStmt(uint32_t FontSize, StringRef FontName, uint32_t FontWeight, + bool FontItalic, uint32_t FontCharset) + : Size(FontSize), Weight(FontWeight), Charset(FontCharset), + Name(FontName), Italic(FontItalic) {} + raw_ostream &log(raw_ostream &) const override; + Twine getResourceTypeName() const override { return "FONT"; } + Error visit(Visitor *V) const override { return V->visitFontStmt(this); } +}; + +// STYLE optional statement. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381051(v=vs.85).aspx +class StyleStmt : public OptionalStmt { +public: + uint32_t Value; + + StyleStmt(uint32_t Style) : Value(Style) {} + raw_ostream &log(raw_ostream &) const override; + Twine getResourceTypeName() const override { return "STYLE"; } + Error visit(Visitor *V) const override { return V->visitStyleStmt(this); } +}; + +// EXSTYLE optional statement. +// +// Ref: docs.microsoft.com/en-us/windows/desktop/menurc/exstyle-statement +class ExStyleStmt : public OptionalStmt { +public: + uint32_t Value; + + ExStyleStmt(uint32_t ExStyle) : Value(ExStyle) {} + raw_ostream &log(raw_ostream &) const override; + Twine getResourceTypeName() const override { return "EXSTYLE"; } + Error visit(Visitor *V) const override { return V->visitExStyleStmt(this); } +}; + +// CLASS optional statement. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380883(v=vs.85).aspx +class ClassStmt : public OptionalStmt { +public: + IntOrString Value; + + ClassStmt(IntOrString Class) : Value(Class) {} + raw_ostream &log(raw_ostream &) const override; + Twine getResourceTypeName() const override { return "CLASS"; } + Error visit(Visitor *V) const override { return V->visitClassStmt(this); } +}; + +} // namespace rc +} // namespace llvm + +#endif diff --git a/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptToken.cpp b/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptToken.cpp new file mode 100644 index 0000000000..a8f40abdf8 --- /dev/null +++ b/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptToken.cpp @@ -0,0 +1,367 @@ +//===-- ResourceScriptToken.cpp ---------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This file implements an interface defined in ResourceScriptToken.h. +// In particular, it defines an .rc script tokenizer. +// +//===---------------------------------------------------------------------===// + +#include "ResourceScriptToken.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/raw_ostream.h" + +#include <algorithm> +#include <cassert> +#include <cctype> +#include <cstdlib> +#include <utility> + +using namespace llvm; + +using Kind = RCToken::Kind; + +// Checks if Representation is a correct description of an RC integer. +// It should be a 32-bit unsigned integer, either decimal, octal (0[0-7]+), +// or hexadecimal (0x[0-9a-f]+). It might be followed by a single 'L' +// character (that is the difference between our representation and +// StringRef's one). If Representation is correct, 'true' is returned and +// the return value is put back in Num. +static bool rcGetAsInteger(StringRef Representation, uint32_t &Num) { + size_t Length = Representation.size(); + if (Length == 0) + return false; + // Strip the last 'L' if unnecessary. + if (std::toupper(Representation.back()) == 'L') + Representation = Representation.drop_back(1); + + return !Representation.getAsInteger<uint32_t>(0, Num); +} + +RCToken::RCToken(RCToken::Kind RCTokenKind, StringRef Value) + : TokenKind(RCTokenKind), TokenValue(Value) {} + +uint32_t RCToken::intValue() const { + assert(TokenKind == Kind::Int); + // We assume that the token already is a correct integer (checked by + // rcGetAsInteger). + uint32_t Result; + bool IsSuccess = rcGetAsInteger(TokenValue, Result); + assert(IsSuccess); + (void)IsSuccess; // Silence the compiler warning when -DNDEBUG flag is on. + 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; } + +bool RCToken::isBinaryOp() const { + switch (TokenKind) { + case Kind::Plus: + case Kind::Minus: + case Kind::Pipe: + case Kind::Amp: + return true; + default: + return false; + } +} + +static Error getStringError(const Twine &message) { + return make_error<StringError>("Error parsing file: " + message, + inconvertibleErrorCode()); +} + +namespace { + +class Tokenizer { +public: + Tokenizer(StringRef Input) : Data(Input), DataLength(Input.size()), Pos(0) {} + + Expected<std::vector<RCToken>> run(); + +private: + // All 'advancing' methods return boolean values; if they're equal to false, + // the stream has ended or failed. + bool advance(size_t Amount = 1); + bool skipWhitespaces(); + + // Consumes a token. If any problem occurred, a non-empty Error is returned. + Error consumeToken(const Kind TokenKind); + + // Check if tokenizer is about to read FollowingChars. + bool willNowRead(StringRef FollowingChars) const; + + // Check if tokenizer can start reading an identifier at current position. + // The original tool did non specify the rules to determine what is a correct + // identifier. We assume they should follow the C convention: + // [a-zA-Z_][a-zA-Z0-9_]*. + bool canStartIdentifier() const; + // Check if tokenizer can continue reading an identifier. + bool canContinueIdentifier() const; + + // Check if tokenizer can start reading an integer. + // A correct integer always starts with a 0-9 digit, + // can contain characters 0-9A-Fa-f (digits), + // Ll (marking the integer is 32-bit), Xx (marking the representation + // is hexadecimal). As some kind of separator should come after the + // integer, we can consume the integer until a non-alphanumeric + // character. + bool canStartInt() const; + bool canContinueInt() const; + + bool canStartString() const; + + // Check if tokenizer can start reading a single line comment (e.g. a comment + // that begins with '//') + bool canStartLineComment() const; + + // Check if tokenizer can start or finish reading a block comment (e.g. a + // comment that begins with '/*' and ends with '*/') + bool canStartBlockComment() const; + + // Throw away all remaining characters on the current line. + void skipCurrentLine(); + + bool streamEof() const; + + // Classify the token that is about to be read from the current position. + Kind classifyCurrentToken() const; + + // Process the Kind::Identifier token - check if it is + // an identifier describing a block start or end. + void processIdentifier(RCToken &token) const; + + StringRef Data; + size_t DataLength, Pos; +}; + +void Tokenizer::skipCurrentLine() { + Pos = Data.find_first_of("\r\n", Pos); + Pos = Data.find_first_not_of("\r\n", Pos); + + if (Pos == StringRef::npos) + Pos = DataLength; +} + +Expected<std::vector<RCToken>> Tokenizer::run() { + Pos = 0; + std::vector<RCToken> Result; + + // Consume an optional UTF-8 Byte Order Mark. + if (willNowRead("\xef\xbb\xbf")) + advance(3); + + while (!streamEof()) { + if (!skipWhitespaces()) + break; + + Kind TokenKind = classifyCurrentToken(); + if (TokenKind == Kind::Invalid) + return getStringError("Invalid token found at position " + Twine(Pos)); + + const size_t TokenStart = Pos; + if (Error TokenError = consumeToken(TokenKind)) + return std::move(TokenError); + + // Comments are just deleted, don't bother saving them. + if (TokenKind == Kind::LineComment || TokenKind == Kind::StartComment) + continue; + + RCToken Token(TokenKind, Data.take_front(Pos).drop_front(TokenStart)); + if (TokenKind == Kind::Identifier) { + processIdentifier(Token); + } else if (TokenKind == Kind::Int) { + uint32_t TokenInt; + if (!rcGetAsInteger(Token.value(), TokenInt)) { + // The integer has incorrect format or cannot be represented in + // a 32-bit integer. + return getStringError("Integer invalid or too large: " + + Token.value().str()); + } + } + + Result.push_back(Token); + } + + return Result; +} + +bool Tokenizer::advance(size_t Amount) { + Pos += Amount; + return !streamEof(); +} + +bool Tokenizer::skipWhitespaces() { + while (!streamEof() && isSpace(Data[Pos])) + advance(); + return !streamEof(); +} + +Error Tokenizer::consumeToken(const Kind TokenKind) { + switch (TokenKind) { + // One-character token consumption. +#define TOKEN(Name) +#define SHORT_TOKEN(Name, Ch) case Kind::Name: +#include "ResourceScriptTokenList.def" + advance(); + return Error::success(); + + case Kind::LineComment: + advance(2); + skipCurrentLine(); + return Error::success(); + + case Kind::StartComment: { + advance(2); + auto EndPos = Data.find("*/", Pos); + if (EndPos == StringRef::npos) + return getStringError( + "Unclosed multi-line comment beginning at position " + Twine(Pos)); + advance(EndPos - Pos); + advance(2); + return Error::success(); + } + case Kind::Identifier: + while (!streamEof() && canContinueIdentifier()) + advance(); + return Error::success(); + + case Kind::Int: + while (!streamEof() && canContinueInt()) + advance(); + return Error::success(); + + case Kind::String: + // Consume the preceding 'L', if there is any. + if (std::toupper(Data[Pos]) == 'L') + advance(); + // Consume the double-quote. + advance(); + + // Consume the characters until the end of the file, line or string. + while (true) { + if (streamEof()) { + return getStringError("Unterminated string literal."); + } else if (Data[Pos] == '"') { + // Consume the ending double-quote. + advance(); + // However, if another '"' follows this double-quote, the string didn't + // end and we just included '"' into the string. + if (!willNowRead("\"")) + return Error::success(); + } else if (Data[Pos] == '\n') { + return getStringError("String literal not terminated in the line."); + } + + advance(); + } + + case Kind::Invalid: + assert(false && "Cannot consume an invalid token."); + } + + llvm_unreachable("Unknown RCToken::Kind"); +} + +bool Tokenizer::willNowRead(StringRef FollowingChars) const { + return Data.drop_front(Pos).startswith(FollowingChars); +} + +bool Tokenizer::canStartIdentifier() const { + assert(!streamEof()); + + const char CurChar = Data[Pos]; + return std::isalpha(CurChar) || CurChar == '_' || CurChar == '.'; +} + +bool Tokenizer::canContinueIdentifier() const { + assert(!streamEof()); + const char CurChar = Data[Pos]; + return std::isalnum(CurChar) || CurChar == '_' || CurChar == '.' || + CurChar == '/' || CurChar == '\\' || CurChar == '-'; +} + +bool Tokenizer::canStartInt() const { + assert(!streamEof()); + return std::isdigit(Data[Pos]); +} + +bool Tokenizer::canStartBlockComment() const { + assert(!streamEof()); + return Data.drop_front(Pos).startswith("/*"); +} + +bool Tokenizer::canStartLineComment() const { + assert(!streamEof()); + return Data.drop_front(Pos).startswith("//"); +} + +bool Tokenizer::canContinueInt() const { + assert(!streamEof()); + return std::isalnum(Data[Pos]); +} + +bool Tokenizer::canStartString() const { + return willNowRead("\"") || willNowRead("L\"") || willNowRead("l\""); +} + +bool Tokenizer::streamEof() const { return Pos == DataLength; } + +Kind Tokenizer::classifyCurrentToken() const { + if (canStartBlockComment()) + return Kind::StartComment; + if (canStartLineComment()) + return Kind::LineComment; + + if (canStartInt()) + return Kind::Int; + if (canStartString()) + return Kind::String; + // BEGIN and END are at this point of lexing recognized as identifiers. + if (canStartIdentifier()) + return Kind::Identifier; + + const char CurChar = Data[Pos]; + + switch (CurChar) { + // One-character token classification. +#define TOKEN(Name) +#define SHORT_TOKEN(Name, Ch) \ + case Ch: \ + return Kind::Name; +#include "ResourceScriptTokenList.def" + + default: + return Kind::Invalid; + } +} + +void Tokenizer::processIdentifier(RCToken &Token) const { + assert(Token.kind() == Kind::Identifier); + StringRef Name = Token.value(); + + if (Name.equals_insensitive("begin")) + Token = RCToken(Kind::BlockBegin, Name); + else if (Name.equals_insensitive("end")) + Token = RCToken(Kind::BlockEnd, Name); +} + +} // anonymous namespace + +namespace llvm { + +Expected<std::vector<RCToken>> tokenizeRC(StringRef Input) { + return Tokenizer(Input).run(); +} + +} // namespace llvm diff --git a/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptToken.h b/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptToken.h new file mode 100644 index 0000000000..cc8ca48b49 --- /dev/null +++ b/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptToken.h @@ -0,0 +1,81 @@ +//===-- ResourceScriptToken.h -----------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This declares the .rc script tokens and defines an interface for tokenizing +// the input data. The list of available tokens is located at +// ResourceScriptTokenList.def. +// +// Note that the tokenizer does not support preprocessor directives. The +// preprocessor should do its work on the .rc file before running llvm-rc. +// +// As for now, it is possible to parse ASCII files only (the behavior on +// UTF files might be undefined). However, it already consumes UTF-8 BOM, if +// there is any. Thus, ASCII-compatible UTF-8 files are tokenized correctly. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380599(v=vs.85).aspx +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_LLVMRC_RESOURCESCRIPTTOKEN_H +#define LLVM_TOOLS_LLVMRC_RESOURCESCRIPTTOKEN_H + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" + +#include <cstdint> +#include <map> +#include <string> +#include <vector> + +namespace llvm { + +// A definition of a single resource script token. Each token has its kind +// (declared in ResourceScriptTokenList) and holds a value - a reference +// representation of the token. +// RCToken does not claim ownership on its value. A memory buffer containing +// the token value should be stored in a safe place and cannot be freed +// nor reallocated. +class RCToken { +public: + enum class Kind { +#define TOKEN(Name) Name, +#define SHORT_TOKEN(Name, Ch) Name, +#include "ResourceScriptTokenList.def" + }; + + RCToken(RCToken::Kind RCTokenKind, StringRef Value); + + // Get an integer value of the integer token. + uint32_t intValue() const; + bool isLongInt() const; + + StringRef value() const; + Kind kind() const; + + // Check if a token describes a binary operator. + bool isBinaryOp() const; + +private: + Kind TokenKind; + StringRef TokenValue; +}; + +// Tokenize Input. +// In case no error occurred, the return value contains +// tokens in order they were in the input file. +// In case of any error, the return value contains +// a textual representation of error. +// +// Tokens returned by this function hold only references to the parts +// of the Input. Memory buffer containing Input cannot be freed, +// modified or reallocated. +Expected<std::vector<RCToken>> tokenizeRC(StringRef Input); + +} // namespace llvm + +#endif diff --git a/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptTokenList.def b/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptTokenList.def new file mode 100644 index 0000000000..a61a96461f --- /dev/null +++ b/contrib/libs/llvm14/tools/llvm-rc/ResourceScriptTokenList.def @@ -0,0 +1,39 @@ +//===-- ResourceScriptTokenList.h -------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This is a part of llvm-rc tokenizer. It lists all the possible tokens +// that might occur in a correct .rc script. +// +//===---------------------------------------------------------------------===// + + +// Long tokens. They might consist of more than one character. +TOKEN(Invalid) // Invalid token. Should not occur in a valid script. +TOKEN(Int) // Integer (decimal, octal or hexadecimal). +TOKEN(String) // String value. +TOKEN(Identifier) // Script identifier (resource name or type). +TOKEN(LineComment) // Beginning of single-line comment. +TOKEN(StartComment) // Beginning of multi-line comment. + +// Short tokens. They usually consist of exactly one character. +// The definitions are of the form SHORT_TOKEN(TokenName, TokenChar). +// TokenChar is the one-character token representation occuring in the correct +// .rc scripts. +SHORT_TOKEN(BlockBegin, '{') // Start of the script block; can also be BEGIN. +SHORT_TOKEN(BlockEnd, '}') // End of the block; can also be END. +SHORT_TOKEN(Comma, ',') // Comma - resource arguments separator. +SHORT_TOKEN(Plus, '+') // Addition operator. +SHORT_TOKEN(Minus, '-') // Subtraction operator. +SHORT_TOKEN(Pipe, '|') // Bitwise-OR operator. +SHORT_TOKEN(Amp, '&') // Bitwise-AND operator. +SHORT_TOKEN(Tilde, '~') // Bitwise-NOT operator. +SHORT_TOKEN(LeftParen, '(') // Left parenthesis in the script expressions. +SHORT_TOKEN(RightParen, ')') // Right parenthesis. + +#undef TOKEN +#undef SHORT_TOKEN diff --git a/contrib/libs/llvm14/tools/llvm-rc/ResourceVisitor.h b/contrib/libs/llvm14/tools/llvm-rc/ResourceVisitor.h new file mode 100644 index 0000000000..843c8d898a --- /dev/null +++ b/contrib/libs/llvm14/tools/llvm-rc/ResourceVisitor.h @@ -0,0 +1,61 @@ +//===-- ResourceVisitor.h ---------------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===---------------------------------------------------------------------===// +// +// This defines a base class visiting resource script resources. +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_LLVMRC_RESOURCEVISITOR_H +#define LLVM_TOOLS_LLVMRC_RESOURCEVISITOR_H + +#include "llvm/Support/Error.h" + +namespace llvm { +namespace rc { + +class RCResource; + +class CaptionStmt; +class ClassStmt; +class CharacteristicsStmt; +class ExStyleStmt; +class FontStmt; +class LanguageResource; +class StyleStmt; +class VersionStmt; + +class Visitor { +public: + virtual Error visitNullResource(const RCResource *) = 0; + virtual Error visitAcceleratorsResource(const RCResource *) = 0; + virtual Error visitBitmapResource(const RCResource *) = 0; + virtual Error visitCursorResource(const RCResource *) = 0; + virtual Error visitDialogResource(const RCResource *) = 0; + virtual Error visitHTMLResource(const RCResource *) = 0; + virtual Error visitIconResource(const RCResource *) = 0; + virtual Error visitMenuResource(const RCResource *) = 0; + virtual Error visitStringTableResource(const RCResource *) = 0; + virtual Error visitUserDefinedResource(const RCResource *) = 0; + virtual Error visitVersionInfoResource(const RCResource *) = 0; + + virtual Error visitCaptionStmt(const CaptionStmt *) = 0; + virtual Error visitClassStmt(const ClassStmt *) = 0; + virtual Error visitCharacteristicsStmt(const CharacteristicsStmt *) = 0; + virtual Error visitExStyleStmt(const ExStyleStmt *) = 0; + virtual Error visitFontStmt(const FontStmt *) = 0; + virtual Error visitLanguageStmt(const LanguageResource *) = 0; + virtual Error visitStyleStmt(const StyleStmt *) = 0; + virtual Error visitVersionStmt(const VersionStmt *) = 0; + + virtual ~Visitor() {} +}; + +} // namespace rc +} // namespace llvm + +#endif diff --git a/contrib/libs/llvm14/tools/llvm-rc/WindresOpts.td b/contrib/libs/llvm14/tools/llvm-rc/WindresOpts.td new file mode 100644 index 0000000000..3c75c85ece --- /dev/null +++ b/contrib/libs/llvm14/tools/llvm-rc/WindresOpts.td @@ -0,0 +1,62 @@ +include "llvm/Option/OptParser.td" + +multiclass Long<string name, string help> { + def NAME: Separate<["--"], name>; + def NAME # _eq: Joined<["--"], name # "=">, Alias<!cast<Separate>(NAME)>, + HelpText<help>; +} + +multiclass LongAlias<string name, Option orig> { + def NAME: Separate<["--"], name>, Alias<orig>; + def NAME # _eq: Joined<["--"], name # "=">, Alias<orig>; +} + +multiclass LongShort<string short, string long, string help> { + def NAME: Separate<["--"], long>; + def NAME # _eq: Joined<["--"], long # "=">, Alias<!cast<Separate>(NAME)>, + HelpText<help>; + def NAME # _short: JoinedOrSeparate<["-"], short>, Alias<!cast<Separate>(NAME)>; +} + +multiclass F<string short, string long, string help> { + def NAME: Flag<["-"], short>; + def NAME # _long: Flag<["--"], long>, Alias<!cast<Flag>(NAME)>, + HelpText<help>; +} + +defm input : LongShort<"i", "input", "Input file">; + +defm output : LongShort<"o", "output", "Output file">; + +defm input_format : LongShort<"J", "input-format", "Input format">; + +defm output_format : LongShort<"O", "output-format", "Output format">; + +defm preprocessor : Long<"preprocessor", "Custom preprocessor command">; +defm preprocessor_arg : Long<"preprocessor-arg", "Preprocessor command argument">; + +defm target : LongShort<"F", "target", "Target BFD format name">; + +defm include_dir : LongShort<"I", "include-dir", "Include directory">; +defm include_alias : LongAlias<"include", include_dir>; + +defm define : LongShort<"D", "define", "Define to pass to the preprocessor">; + +defm undef : LongShort<"U", "undefine", "Undefine to pass to the preprocessor">; + +defm codepage : LongShort<"c", "codepage", "Default codepage to use">; + +defm language : LongShort<"l", "language", "Default language to use (0x0-0xffff)">; + +defm verbose : F<"v", "verbose", "Enable verbose output">; +defm version : F<"V", "version", "Display version">; + +defm help : F<"h", "help", "Display this message and exit">; + +// Print (but do not run) the commands to run for preprocessing +def _HASH_HASH_HASH : Flag<["-"], "###">; + +def no_preprocess : Flag<["--"], "no-preprocess">; + +// Unimplemented options for compatibility +def use_temp_file: Flag<["--"], "use-temp-file">; diff --git a/contrib/libs/llvm14/tools/llvm-rc/llvm-rc.cpp b/contrib/libs/llvm14/tools/llvm-rc/llvm-rc.cpp new file mode 100644 index 0000000000..d43994deec --- /dev/null +++ b/contrib/libs/llvm14/tools/llvm-rc/llvm-rc.cpp @@ -0,0 +1,755 @@ +//===-- llvm-rc.cpp - Compile .rc scripts into .res -------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Compile .rc scripts into .res files. This is intended to be a +// platform-independent port of Microsoft's rc.exe tool. +// +//===----------------------------------------------------------------------===// + +#include "ResourceFileWriter.h" +#include "ResourceScriptCppFilter.h" +#include "ResourceScriptParser.h" +#include "ResourceScriptStmt.h" +#include "ResourceScriptToken.h" + +#include "llvm/ADT/Triple.h" +#include "llvm/Object/WindowsResource.h" +#include "llvm/Option/Arg.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/FileUtilities.h" +#include "llvm/Support/Host.h" +#include "llvm/Support/InitLLVM.h" +#include "llvm/Support/ManagedStatic.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/PrettyStackTrace.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/Program.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/StringSaver.h" +#include "llvm/Support/raw_ostream.h" + +#include <algorithm> +#include <system_error> + +using namespace llvm; +using namespace llvm::rc; + +namespace { + +// Input options tables. + +enum ID { + OPT_INVALID = 0, // This is not a correct option ID. +#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ + HELPTEXT, METAVAR, VALUES) \ + OPT_##ID, +#include "Opts.inc" +#undef OPTION +}; + +#define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE; +#include "Opts.inc" +#undef PREFIX + +const opt::OptTable::Info InfoTable[] = { +#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ + HELPTEXT, METAVAR, VALUES) \ + { \ + PREFIX, NAME, HELPTEXT, \ + METAVAR, OPT_##ID, opt::Option::KIND##Class, \ + PARAM, FLAGS, OPT_##GROUP, \ + OPT_##ALIAS, ALIASARGS, VALUES}, +#include "Opts.inc" +#undef OPTION +}; + +class RcOptTable : public opt::OptTable { +public: + RcOptTable() : OptTable(InfoTable, /* IgnoreCase = */ true) {} +}; + +enum Windres_ID { + WINDRES_INVALID = 0, // This is not a correct option ID. +#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ + HELPTEXT, METAVAR, VALUES) \ + WINDRES_##ID, +#include "WindresOpts.inc" +#undef OPTION +}; + +#define PREFIX(NAME, VALUE) const char *const WINDRES_##NAME[] = VALUE; +#include "WindresOpts.inc" +#undef PREFIX + +const opt::OptTable::Info WindresInfoTable[] = { +#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ + HELPTEXT, METAVAR, VALUES) \ + { \ + WINDRES_##PREFIX, NAME, HELPTEXT, \ + METAVAR, WINDRES_##ID, opt::Option::KIND##Class, \ + PARAM, FLAGS, WINDRES_##GROUP, \ + WINDRES_##ALIAS, ALIASARGS, VALUES}, +#include "WindresOpts.inc" +#undef OPTION +}; + +class WindresOptTable : public opt::OptTable { +public: + WindresOptTable() : OptTable(WindresInfoTable, /* IgnoreCase = */ false) {} +}; + +static ExitOnError ExitOnErr; +static FileRemover TempPreprocFile; +static FileRemover TempResFile; + +[[noreturn]] static void fatalError(const Twine &Message) { + errs() << Message << "\n"; + exit(1); +} + +std::string createTempFile(const Twine &Prefix, StringRef Suffix) { + std::error_code EC; + SmallString<128> FileName; + if ((EC = sys::fs::createTemporaryFile(Prefix, Suffix, FileName))) + fatalError("Unable to create temp file: " + EC.message()); + return static_cast<std::string>(FileName); +} + +ErrorOr<std::string> findClang(const char *Argv0) { + StringRef Parent = llvm::sys::path::parent_path(Argv0); + ErrorOr<std::string> Path = std::error_code(); + if (!Parent.empty()) { + // First look for the tool with all potential names in the specific + // directory of Argv0, if known + for (const auto *Name : {"clang", "clang-cl"}) { + Path = sys::findProgramByName(Name, Parent); + if (Path) + return Path; + } + } + // If no parent directory known, or not found there, look everywhere in PATH + for (const auto *Name : {"clang", "clang-cl"}) { + Path = sys::findProgramByName(Name); + if (Path) + return Path; + } + return Path; +} + +bool isUsableArch(Triple::ArchType Arch) { + switch (Arch) { + case Triple::x86: + case Triple::x86_64: + case Triple::arm: + case Triple::thumb: + case Triple::aarch64: + // These work properly with the clang driver, setting the expected + // defines such as _WIN32 etc. + return true; + default: + // Other archs aren't set up for use with windows as target OS, (clang + // doesn't define e.g. _WIN32 etc), so with them we need to set a + // different default arch. + return false; + } +} + +Triple::ArchType getDefaultFallbackArch() { + return Triple::x86_64; +} + +std::string getClangClTriple() { + Triple T(sys::getDefaultTargetTriple()); + if (!isUsableArch(T.getArch())) + T.setArch(getDefaultFallbackArch()); + T.setOS(Triple::Win32); + T.setVendor(Triple::PC); + T.setEnvironment(Triple::MSVC); + T.setObjectFormat(Triple::COFF); + return T.str(); +} + +std::string getMingwTriple() { + Triple T(sys::getDefaultTargetTriple()); + if (!isUsableArch(T.getArch())) + T.setArch(getDefaultFallbackArch()); + if (T.isWindowsGNUEnvironment()) + return T.str(); + // Write out the literal form of the vendor/env here, instead of + // constructing them with enum values (which end up with them in + // normalized form). The literal form of the triple can matter for + // finding include files. + return (Twine(T.getArchName()) + "-w64-mingw32").str(); +} + +enum Format { Rc, Res, Coff, Unknown }; + +struct RcOptions { + bool Preprocess = true; + bool PrintCmdAndExit = false; + std::string Triple; + std::vector<std::string> PreprocessCmd; + std::vector<std::string> PreprocessArgs; + + std::string InputFile; + Format InputFormat = Rc; + std::string OutputFile; + Format OutputFormat = Res; + + bool BeVerbose = false; + WriterParams Params; + bool AppendNull = false; + bool IsDryRun = false; + // Set the default language; choose en-US arbitrarily. + unsigned LangId = (/*PrimaryLangId*/ 0x09) | (/*SubLangId*/ 0x01 << 10); +}; + +bool preprocess(StringRef Src, StringRef Dst, const RcOptions &Opts, + const char *Argv0) { + std::string Clang; + if (Opts.PrintCmdAndExit) { + Clang = "clang"; + } else { + ErrorOr<std::string> ClangOrErr = findClang(Argv0); + if (ClangOrErr) { + Clang = *ClangOrErr; + } else { + errs() << "llvm-rc: Unable to find clang, skipping preprocessing." + << "\n"; + errs() << "Pass -no-cpp to disable preprocessing. This will be an error " + "in the future." + << "\n"; + return false; + } + } + + SmallVector<StringRef, 8> Args = { + Clang, "--driver-mode=gcc", "-target", Opts.Triple, "-E", + "-xc", "-DRC_INVOKED"}; + if (!Opts.PreprocessCmd.empty()) { + Args.clear(); + for (const auto &S : Opts.PreprocessCmd) + Args.push_back(S); + } + Args.push_back(Src); + Args.push_back("-o"); + Args.push_back(Dst); + for (const auto &S : Opts.PreprocessArgs) + Args.push_back(S); + if (Opts.PrintCmdAndExit || Opts.BeVerbose) { + for (const auto &A : Args) { + outs() << " "; + sys::printArg(outs(), A, Opts.PrintCmdAndExit); + } + outs() << "\n"; + if (Opts.PrintCmdAndExit) + exit(0); + } + // The llvm Support classes don't handle reading from stdout of a child + // process; otherwise we could avoid using a temp file. + int Res = sys::ExecuteAndWait(Clang, Args); + if (Res) { + fatalError("llvm-rc: Preprocessing failed."); + } + return true; +} + +static std::pair<bool, std::string> isWindres(llvm::StringRef Argv0) { + StringRef ProgName = llvm::sys::path::stem(Argv0); + // x86_64-w64-mingw32-windres -> x86_64-w64-mingw32, windres + // llvm-rc -> "", llvm-rc + // aarch64-w64-mingw32-llvm-windres-10.exe -> aarch64-w64-mingw32, llvm-windres + ProgName = ProgName.rtrim("0123456789.-"); + if (!ProgName.consume_back_insensitive("windres")) + return std::make_pair<bool, std::string>(false, ""); + ProgName.consume_back_insensitive("llvm-"); + ProgName.consume_back_insensitive("-"); + return std::make_pair<bool, std::string>(true, ProgName.str()); +} + +Format parseFormat(StringRef S) { + Format F = StringSwitch<Format>(S.lower()) + .Case("rc", Rc) + .Case("res", Res) + .Case("coff", Coff) + .Default(Unknown); + if (F == Unknown) + fatalError("Unable to parse '" + Twine(S) + "' as a format"); + return F; +} + +void deduceFormat(Format &Dest, StringRef File) { + Format F = StringSwitch<Format>(sys::path::extension(File.lower())) + .Case(".rc", Rc) + .Case(".res", Res) + .Case(".o", Coff) + .Case(".obj", Coff) + .Default(Unknown); + if (F != Unknown) + Dest = F; +} + +std::string unescape(StringRef S) { + std::string Out; + Out.reserve(S.size()); + for (int I = 0, E = S.size(); I < E; I++) { + if (S[I] == '\\') { + if (I + 1 < E) + Out.push_back(S[++I]); + else + fatalError("Unterminated escape"); + continue; + } + Out.push_back(S[I]); + } + return Out; +} + +std::vector<std::string> unescapeSplit(StringRef S) { + std::vector<std::string> OutArgs; + std::string Out; + bool InQuote = false; + for (int I = 0, E = S.size(); I < E; I++) { + if (S[I] == '\\') { + if (I + 1 < E) + Out.push_back(S[++I]); + else + fatalError("Unterminated escape"); + continue; + } + if (S[I] == '"') { + InQuote = !InQuote; + continue; + } + if (S[I] == ' ' && !InQuote) { + OutArgs.push_back(Out); + Out.clear(); + continue; + } + Out.push_back(S[I]); + } + if (InQuote) + fatalError("Unterminated quote"); + if (!Out.empty()) + OutArgs.push_back(Out); + return OutArgs; +} + +RcOptions parseWindresOptions(ArrayRef<const char *> ArgsArr, + ArrayRef<const char *> InputArgsArray, + std::string Prefix) { + WindresOptTable T; + RcOptions Opts; + unsigned MAI, MAC; + opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MAI, MAC); + + // The tool prints nothing when invoked with no command-line arguments. + if (InputArgs.hasArg(WINDRES_help)) { + T.printHelp(outs(), "windres [options] file...", + "LLVM windres (GNU windres compatible)", false, true); + exit(0); + } + + if (InputArgs.hasArg(WINDRES_version)) { + outs() << "llvm-windres, compatible with GNU windres\n"; + cl::PrintVersionMessage(); + exit(0); + } + + std::vector<std::string> FileArgs = InputArgs.getAllArgValues(WINDRES_INPUT); + FileArgs.insert(FileArgs.end(), InputArgsArray.begin(), InputArgsArray.end()); + + if (InputArgs.hasArg(WINDRES_input)) { + Opts.InputFile = InputArgs.getLastArgValue(WINDRES_input).str(); + } else if (!FileArgs.empty()) { + Opts.InputFile = FileArgs.front(); + FileArgs.erase(FileArgs.begin()); + } else { + // TODO: GNU windres takes input on stdin in this case. + fatalError("Missing input file"); + } + + if (InputArgs.hasArg(WINDRES_output)) { + Opts.OutputFile = InputArgs.getLastArgValue(WINDRES_output).str(); + } else if (!FileArgs.empty()) { + Opts.OutputFile = FileArgs.front(); + FileArgs.erase(FileArgs.begin()); + } else { + // TODO: GNU windres writes output in rc form to stdout in this case. + fatalError("Missing output file"); + } + + if (InputArgs.hasArg(WINDRES_input_format)) { + Opts.InputFormat = + parseFormat(InputArgs.getLastArgValue(WINDRES_input_format)); + } else { + deduceFormat(Opts.InputFormat, Opts.InputFile); + } + if (Opts.InputFormat == Coff) + fatalError("Unsupported input format"); + + if (InputArgs.hasArg(WINDRES_output_format)) { + Opts.OutputFormat = + parseFormat(InputArgs.getLastArgValue(WINDRES_output_format)); + } else { + // The default in windres differs from the default in RcOptions + Opts.OutputFormat = Coff; + deduceFormat(Opts.OutputFormat, Opts.OutputFile); + } + if (Opts.OutputFormat == Rc) + fatalError("Unsupported output format"); + if (Opts.InputFormat == Opts.OutputFormat) { + outs() << "Nothing to do.\n"; + exit(0); + } + + Opts.PrintCmdAndExit = InputArgs.hasArg(WINDRES__HASH_HASH_HASH); + Opts.Preprocess = !InputArgs.hasArg(WINDRES_no_preprocess); + Triple TT(Prefix); + if (InputArgs.hasArg(WINDRES_target)) { + StringRef Value = InputArgs.getLastArgValue(WINDRES_target); + if (Value == "pe-i386") + Opts.Triple = "i686-w64-mingw32"; + else if (Value == "pe-x86-64") + Opts.Triple = "x86_64-w64-mingw32"; + else + // Implicit extension; if the --target value isn't one of the known + // BFD targets, allow setting the full triple string via this instead. + Opts.Triple = Value.str(); + } else if (TT.getArch() != Triple::UnknownArch) + Opts.Triple = Prefix; + else + Opts.Triple = getMingwTriple(); + + for (const auto *Arg : + InputArgs.filtered(WINDRES_include_dir, WINDRES_define, WINDRES_undef, + WINDRES_preprocessor_arg)) { + // GNU windres passes the arguments almost as-is on to popen() (it only + // backslash escapes spaces in the arguments), where a shell would + // unescape backslash escapes for quotes and similar. This means that + // when calling GNU windres, callers need to double escape chars like + // quotes, e.g. as -DSTRING=\\\"1.2.3\\\". + // + // Exactly how the arguments are interpreted depends on the platform + // though - but the cases where this matters (where callers would have + // done this double escaping) probably is confined to cases like these + // quoted string defines, and those happen to work the same across unix + // and windows. + std::string Unescaped = unescape(Arg->getValue()); + switch (Arg->getOption().getID()) { + case WINDRES_include_dir: + // Technically, these are handled the same way as e.g. defines, but + // the way we consistently unescape the unix way breaks windows paths + // with single backslashes. Alternatively, our unescape function would + // need to mimic the platform specific command line parsing/unescaping + // logic. + Opts.Params.Include.push_back(Arg->getValue()); + Opts.PreprocessArgs.push_back("-I"); + Opts.PreprocessArgs.push_back(Arg->getValue()); + break; + case WINDRES_define: + Opts.PreprocessArgs.push_back("-D"); + Opts.PreprocessArgs.push_back(Unescaped); + break; + case WINDRES_undef: + Opts.PreprocessArgs.push_back("-U"); + Opts.PreprocessArgs.push_back(Unescaped); + break; + case WINDRES_preprocessor_arg: + Opts.PreprocessArgs.push_back(Unescaped); + break; + } + } + if (InputArgs.hasArg(WINDRES_preprocessor)) + Opts.PreprocessCmd = + unescapeSplit(InputArgs.getLastArgValue(WINDRES_preprocessor)); + + Opts.Params.CodePage = CpWin1252; // Different default + if (InputArgs.hasArg(WINDRES_codepage)) { + if (InputArgs.getLastArgValue(WINDRES_codepage) + .getAsInteger(0, Opts.Params.CodePage)) + fatalError("Invalid code page: " + + InputArgs.getLastArgValue(WINDRES_codepage)); + } + if (InputArgs.hasArg(WINDRES_language)) { + StringRef Val = InputArgs.getLastArgValue(WINDRES_language); + Val.consume_front_insensitive("0x"); + if (Val.getAsInteger(16, Opts.LangId)) + fatalError("Invalid language id: " + + InputArgs.getLastArgValue(WINDRES_language)); + } + + Opts.BeVerbose = InputArgs.hasArg(WINDRES_verbose); + + return Opts; +} + +RcOptions parseRcOptions(ArrayRef<const char *> ArgsArr, + ArrayRef<const char *> InputArgsArray) { + RcOptTable T; + RcOptions Opts; + unsigned MAI, MAC; + opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MAI, MAC); + + // The tool prints nothing when invoked with no command-line arguments. + if (InputArgs.hasArg(OPT_help)) { + T.printHelp(outs(), "rc [options] file...", "Resource Converter", false); + exit(0); + } + + std::vector<std::string> InArgsInfo = InputArgs.getAllArgValues(OPT_INPUT); + InArgsInfo.insert(InArgsInfo.end(), InputArgsArray.begin(), + InputArgsArray.end()); + if (InArgsInfo.size() != 1) { + fatalError("Exactly one input file should be provided."); + } + + Opts.PrintCmdAndExit = InputArgs.hasArg(OPT__HASH_HASH_HASH); + Opts.Triple = getClangClTriple(); + for (const auto *Arg : + InputArgs.filtered(OPT_includepath, OPT_define, OPT_undef)) { + switch (Arg->getOption().getID()) { + case OPT_includepath: + Opts.PreprocessArgs.push_back("-I"); + break; + case OPT_define: + Opts.PreprocessArgs.push_back("-D"); + break; + case OPT_undef: + Opts.PreprocessArgs.push_back("-U"); + break; + } + Opts.PreprocessArgs.push_back(Arg->getValue()); + } + + Opts.InputFile = InArgsInfo[0]; + Opts.BeVerbose = InputArgs.hasArg(OPT_verbose); + Opts.Preprocess = !InputArgs.hasArg(OPT_no_preprocess); + Opts.Params.Include = InputArgs.getAllArgValues(OPT_includepath); + Opts.Params.NoInclude = InputArgs.hasArg(OPT_noinclude); + if (Opts.Params.NoInclude) { + // Clear the INLCUDE variable for the external preprocessor +#ifdef _WIN32 + ::_putenv("INCLUDE="); +#else + ::unsetenv("INCLUDE"); +#endif + } + if (InputArgs.hasArg(OPT_codepage)) { + if (InputArgs.getLastArgValue(OPT_codepage) + .getAsInteger(10, Opts.Params.CodePage)) + fatalError("Invalid code page: " + + InputArgs.getLastArgValue(OPT_codepage)); + } + Opts.IsDryRun = InputArgs.hasArg(OPT_dry_run); + auto OutArgsInfo = InputArgs.getAllArgValues(OPT_fileout); + if (OutArgsInfo.empty()) { + SmallString<128> OutputFile(Opts.InputFile); + llvm::sys::fs::make_absolute(OutputFile); + llvm::sys::path::replace_extension(OutputFile, "res"); + OutArgsInfo.push_back(std::string(OutputFile.str())); + } + if (!Opts.IsDryRun) { + if (OutArgsInfo.size() != 1) + fatalError( + "No more than one output file should be provided (using /FO flag)."); + Opts.OutputFile = OutArgsInfo[0]; + } + Opts.AppendNull = InputArgs.hasArg(OPT_add_null); + if (InputArgs.hasArg(OPT_lang_id)) { + StringRef Val = InputArgs.getLastArgValue(OPT_lang_id); + Val.consume_front_insensitive("0x"); + if (Val.getAsInteger(16, Opts.LangId)) + fatalError("Invalid language id: " + + InputArgs.getLastArgValue(OPT_lang_id)); + } + return Opts; +} + +RcOptions getOptions(const char *Argv0, ArrayRef<const char *> ArgsArr, + ArrayRef<const char *> InputArgs) { + std::string Prefix; + bool IsWindres; + std::tie(IsWindres, Prefix) = isWindres(Argv0); + if (IsWindres) + return parseWindresOptions(ArgsArr, InputArgs, Prefix); + else + return parseRcOptions(ArgsArr, InputArgs); +} + +void doRc(std::string Src, std::string Dest, RcOptions &Opts, + const char *Argv0) { + std::string PreprocessedFile = Src; + if (Opts.Preprocess) { + std::string OutFile = createTempFile("preproc", "rc"); + TempPreprocFile.setFile(OutFile); + if (preprocess(Src, OutFile, Opts, Argv0)) + PreprocessedFile = OutFile; + } + + // Read and tokenize the input file. + ErrorOr<std::unique_ptr<MemoryBuffer>> File = + MemoryBuffer::getFile(PreprocessedFile); + if (!File) { + fatalError("Error opening file '" + Twine(PreprocessedFile) + + "': " + File.getError().message()); + } + + std::unique_ptr<MemoryBuffer> FileContents = std::move(*File); + StringRef Contents = FileContents->getBuffer(); + + std::string FilteredContents = filterCppOutput(Contents); + std::vector<RCToken> Tokens = ExitOnErr(tokenizeRC(FilteredContents)); + + if (Opts.BeVerbose) { + const Twine TokenNames[] = { +#define TOKEN(Name) #Name, +#define SHORT_TOKEN(Name, Ch) #Name, +#include "ResourceScriptTokenList.def" + }; + + for (const RCToken &Token : Tokens) { + outs() << TokenNames[static_cast<int>(Token.kind())] << ": " + << Token.value(); + if (Token.kind() == RCToken::Kind::Int) + outs() << "; int value = " << Token.intValue(); + + outs() << "\n"; + } + } + + WriterParams &Params = Opts.Params; + SmallString<128> InputFile(Src); + llvm::sys::fs::make_absolute(InputFile); + Params.InputFilePath = InputFile; + + switch (Params.CodePage) { + case CpAcp: + case CpWin1252: + case CpUtf8: + break; + default: + fatalError("Unsupported code page, only 0, 1252 and 65001 are supported!"); + } + + std::unique_ptr<ResourceFileWriter> Visitor; + + if (!Opts.IsDryRun) { + std::error_code EC; + auto FOut = std::make_unique<raw_fd_ostream>( + Dest, EC, sys::fs::FA_Read | sys::fs::FA_Write); + if (EC) + fatalError("Error opening output file '" + Dest + "': " + EC.message()); + Visitor = std::make_unique<ResourceFileWriter>(Params, std::move(FOut)); + Visitor->AppendNull = Opts.AppendNull; + + ExitOnErr(NullResource().visit(Visitor.get())); + + unsigned PrimaryLangId = Opts.LangId & 0x3ff; + unsigned SubLangId = Opts.LangId >> 10; + ExitOnErr(LanguageResource(PrimaryLangId, SubLangId).visit(Visitor.get())); + } + + rc::RCParser Parser{std::move(Tokens)}; + while (!Parser.isEof()) { + auto Resource = ExitOnErr(Parser.parseSingleResource()); + if (Opts.BeVerbose) + Resource->log(outs()); + if (!Opts.IsDryRun) + ExitOnErr(Resource->visit(Visitor.get())); + } + + // STRINGTABLE resources come at the very end. + if (!Opts.IsDryRun) + ExitOnErr(Visitor->dumpAllStringTables()); +} + +void doCvtres(std::string Src, std::string Dest, std::string TargetTriple) { + object::WindowsResourceParser Parser; + + ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr = + MemoryBuffer::getFile(Src); + if (!BufferOrErr) + fatalError("Error opening file '" + Twine(Src) + + "': " + BufferOrErr.getError().message()); + std::unique_ptr<MemoryBuffer> &Buffer = BufferOrErr.get(); + std::unique_ptr<object::WindowsResource> Binary = + ExitOnErr(object::WindowsResource::createWindowsResource( + Buffer->getMemBufferRef())); + + std::vector<std::string> Duplicates; + ExitOnErr(Parser.parse(Binary.get(), Duplicates)); + for (const auto &DupeDiag : Duplicates) + fatalError("Duplicate resources: " + DupeDiag); + + Triple T(TargetTriple); + COFF::MachineTypes MachineType; + switch (T.getArch()) { + case Triple::x86: + MachineType = COFF::IMAGE_FILE_MACHINE_I386; + break; + case Triple::x86_64: + MachineType = COFF::IMAGE_FILE_MACHINE_AMD64; + break; + case Triple::arm: + case Triple::thumb: + MachineType = COFF::IMAGE_FILE_MACHINE_ARMNT; + break; + case Triple::aarch64: + MachineType = COFF::IMAGE_FILE_MACHINE_ARM64; + break; + default: + fatalError("Unsupported architecture in target '" + Twine(TargetTriple) + + "'"); + } + + std::unique_ptr<MemoryBuffer> OutputBuffer = + ExitOnErr(object::writeWindowsResourceCOFF(MachineType, Parser, + /*DateTimeStamp*/ 0)); + std::unique_ptr<FileOutputBuffer> FileBuffer = + ExitOnErr(FileOutputBuffer::create(Dest, OutputBuffer->getBufferSize())); + std::copy(OutputBuffer->getBufferStart(), OutputBuffer->getBufferEnd(), + FileBuffer->getBufferStart()); + ExitOnErr(FileBuffer->commit()); +} + +} // anonymous namespace + +int main(int Argc, const char **Argv) { + InitLLVM X(Argc, Argv); + ExitOnErr.setBanner("llvm-rc: "); + + const char **DashDash = std::find_if( + Argv + 1, Argv + Argc, [](StringRef Str) { return Str == "--"; }); + ArrayRef<const char *> ArgsArr = makeArrayRef(Argv + 1, DashDash); + ArrayRef<const char *> FileArgsArr; + if (DashDash != Argv + Argc) + FileArgsArr = makeArrayRef(DashDash + 1, Argv + Argc); + + RcOptions Opts = getOptions(Argv[0], ArgsArr, FileArgsArr); + + std::string ResFile = Opts.OutputFile; + if (Opts.InputFormat == Rc) { + if (Opts.OutputFormat == Coff) { + ResFile = createTempFile("rc", "res"); + TempResFile.setFile(ResFile); + } + doRc(Opts.InputFile, ResFile, Opts, Argv[0]); + } else { + ResFile = Opts.InputFile; + } + if (Opts.OutputFormat == Coff) { + doCvtres(ResFile, Opts.OutputFile, Opts.Triple); + } + + return 0; +} diff --git a/contrib/libs/llvm14/tools/llvm-rc/ya.make b/contrib/libs/llvm14/tools/llvm-rc/ya.make new file mode 100644 index 0000000000..6b75c5a0c1 --- /dev/null +++ b/contrib/libs/llvm14/tools/llvm-rc/ya.make @@ -0,0 +1,44 @@ +# Generated by devtools/yamaker. + +PROGRAM() + +LICENSE(Apache-2.0 WITH LLVM-exception) + +LICENSE_TEXTS(.yandex_meta/licenses.list.txt) + +PEERDIR( + contrib/libs/llvm14 + contrib/libs/llvm14/include + contrib/libs/llvm14/lib/BinaryFormat + contrib/libs/llvm14/lib/Bitcode/Reader + contrib/libs/llvm14/lib/Bitstream/Reader + contrib/libs/llvm14/lib/Demangle + contrib/libs/llvm14/lib/IR + contrib/libs/llvm14/lib/MC + contrib/libs/llvm14/lib/MC/MCParser + contrib/libs/llvm14/lib/Object + contrib/libs/llvm14/lib/Option + contrib/libs/llvm14/lib/Remarks + contrib/libs/llvm14/lib/Support + contrib/libs/llvm14/lib/TextAPI +) + +ADDINCL( + ${ARCADIA_BUILD_ROOT}/contrib/libs/llvm14/tools/llvm-rc + contrib/libs/llvm14/tools/llvm-rc +) + +NO_COMPILER_WARNINGS() + +NO_UTIL() + +SRCS( + ResourceFileWriter.cpp + ResourceScriptCppFilter.cpp + ResourceScriptParser.cpp + ResourceScriptStmt.cpp + ResourceScriptToken.cpp + llvm-rc.cpp +) + +END() |