Index: llvm/test/tools/llvm-rc/Inputs/tag-html-wrong.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-html-wrong.rc @@ -0,0 +1 @@ +1 HTML "some-really-nonexistent-file.html" Index: llvm/test/tools/llvm-rc/Inputs/tag-html.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-html.rc @@ -0,0 +1,2 @@ +100 HTML "webpage1.html" +Kitten HTML "webpage2.html" Index: llvm/test/tools/llvm-rc/Inputs/webpage1.html =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/webpage1.html @@ -0,0 +1,5 @@ + + + Hello! + + Index: llvm/test/tools/llvm-rc/Inputs/webpage2.html =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/webpage2.html @@ -0,0 +1,2 @@ + + Index: llvm/test/tools/llvm-rc/helpmsg.test =================================================================== --- llvm/test/tools/llvm-rc/helpmsg.test +++ llvm/test/tools/llvm-rc/helpmsg.test @@ -7,6 +7,7 @@ ; CHECK-DAG: USAGE: rc [options] ; CHECK-DAG: OPTIONS: ; CHECK-NEXT: /? Display this help and exit. +; CHECK-NEXT: /dry-run Don't compile the input; only try to parse it. ; CHECK-NEXT: /D Define a symbol for the C preprocessor. ; CHECK-NEXT: /FO Change the output file location. ; CHECK-NEXT: /H Display this help and exit. Index: llvm/test/tools/llvm-rc/parser-expr.test =================================================================== --- llvm/test/tools/llvm-rc/parser-expr.test +++ llvm/test/tools/llvm-rc/parser-expr.test @@ -1,4 +1,4 @@ -; RUN: llvm-rc /V %p/Inputs/parser-expr.rc | FileCheck %s +; RUN: llvm-rc /dry-run /V %p/Inputs/parser-expr.rc | FileCheck %s ; CHECK: Language: 5, Sublanguage: 1 ; CHECK-NEXT: Language: 3, Sublanguage: 2 @@ -17,36 +17,36 @@ ; CHECK-NEXT: Language: 5, Sublanguage: 7 -; RUN: not llvm-rc /V %p/Inputs/parser-expr-bad-binary-1.rc 2>&1 | FileCheck %s --check-prefix BINARY1 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-expr-bad-binary-1.rc 2>&1 | FileCheck %s --check-prefix BINARY1 ; BINARY1: llvm-rc: Error parsing file: expected '-', '~', integer or '(', got & -; RUN: not llvm-rc /V %p/Inputs/parser-expr-bad-binary-2.rc 2>&1 | FileCheck %s --check-prefix BINARY2 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-expr-bad-binary-2.rc 2>&1 | FileCheck %s --check-prefix BINARY2 ; BINARY2: llvm-rc: Error parsing file: expected '-', '~', integer or '(', got | -; RUN: not llvm-rc /V %p/Inputs/parser-expr-bad-binary-3.rc 2>&1 | FileCheck %s --check-prefix BINARY3 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-expr-bad-binary-3.rc 2>&1 | FileCheck %s --check-prefix BINARY3 ; BINARY3: llvm-rc: Error parsing file: expected '-', '~', integer or '(', got + -; RUN: not llvm-rc /V %p/Inputs/parser-expr-bad-unary.rc 2>&1 | FileCheck %s --check-prefix UNARY +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-expr-bad-unary.rc 2>&1 | FileCheck %s --check-prefix UNARY ; UNARY: llvm-rc: Error parsing file: expected ',', got ~ -; RUN: not llvm-rc /V %p/Inputs/parser-expr-unbalanced-1.rc 2>&1 | FileCheck %s --check-prefix UNBALANCED1 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-expr-unbalanced-1.rc 2>&1 | FileCheck %s --check-prefix UNBALANCED1 ; UNBALANCED1: llvm-rc: Error parsing file: expected ')', got , -; RUN: not llvm-rc /V %p/Inputs/parser-expr-unbalanced-2.rc 2>&1 | FileCheck %s --check-prefix UNBALANCED2 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-expr-unbalanced-2.rc 2>&1 | FileCheck %s --check-prefix UNBALANCED2 ; UNBALANCED2: llvm-rc: Error parsing file: expected ',', got ) -; RUN: not llvm-rc /V %p/Inputs/parser-expr-unbalanced-3.rc 2>&1 | FileCheck %s --check-prefix UNBALANCED3 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-expr-unbalanced-3.rc 2>&1 | FileCheck %s --check-prefix UNBALANCED3 ; UNBALANCED3: llvm-rc: Error parsing file: expected ',', got ) Index: llvm/test/tools/llvm-rc/parser.test =================================================================== --- llvm/test/tools/llvm-rc/parser.test +++ llvm/test/tools/llvm-rc/parser.test @@ -1,4 +1,4 @@ -; RUN: llvm-rc /V %p/Inputs/parser-correct-everything.rc | FileCheck %s --check-prefix PGOOD +; RUN: llvm-rc /dry-run /V %p/Inputs/parser-correct-everything.rc | FileCheck %s --check-prefix PGOOD ; PGOOD: Icon (meh): "hello.bmp" ; PGOOD-NEXT: Icon (Icon): "Icon" @@ -100,151 +100,151 @@ -; RUN: not llvm-rc /V %p/Inputs/parser-stringtable-no-string.rc 2>&1 | FileCheck %s --check-prefix PSTRINGTABLE1 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-stringtable-no-string.rc 2>&1 | FileCheck %s --check-prefix PSTRINGTABLE1 ; PSTRINGTABLE1: llvm-rc: Error parsing file: expected string, got } -; RUN: not llvm-rc /V %p/Inputs/parser-stringtable-weird-option.rc 2>&1 | FileCheck %s --check-prefix PSTRINGTABLE2 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-stringtable-weird-option.rc 2>&1 | FileCheck %s --check-prefix PSTRINGTABLE2 ; PSTRINGTABLE2: llvm-rc: Error parsing file: expected optional statement type, BEGIN or '{', got NONSENSETYPE -; RUN: not llvm-rc /V %p/Inputs/parser-eof.rc 2>&1 | FileCheck %s --check-prefix PEOF +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-eof.rc 2>&1 | FileCheck %s --check-prefix PEOF ; PEOF: llvm-rc: Error parsing file: expected '-', '~', integer or '(', got -; RUN: not llvm-rc /V %p/Inputs/parser-no-characteristics-arg.rc 2>&1 | FileCheck %s --check-prefix PCHARACTERISTICS1 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-no-characteristics-arg.rc 2>&1 | FileCheck %s --check-prefix PCHARACTERISTICS1 ; PCHARACTERISTICS1: llvm-rc: Error parsing file: expected '-', '~', integer or '(', got BEGIN -; RUN: not llvm-rc /V %p/Inputs/parser-nonsense-token.rc 2>&1 | FileCheck %s --check-prefix PNONSENSE1 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-nonsense-token.rc 2>&1 | FileCheck %s --check-prefix PNONSENSE1 ; PNONSENSE1: llvm-rc: Error parsing file: expected int or identifier, got & -; RUN: not llvm-rc /V %p/Inputs/parser-nonsense-type.rc 2>&1 | FileCheck %s --check-prefix PNONSENSE2 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-nonsense-type.rc 2>&1 | FileCheck %s --check-prefix PNONSENSE2 ; PNONSENSE2: llvm-rc: Error parsing file: expected filename, '{' or BEGIN, got -; RUN: not llvm-rc /V %p/Inputs/parser-nonsense-type-eof.rc 2>&1 | FileCheck %s --check-prefix PNONSENSE3 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-nonsense-type-eof.rc 2>&1 | FileCheck %s --check-prefix PNONSENSE3 ; PNONSENSE3: llvm-rc: Error parsing file: expected int or identifier, got -; RUN: not llvm-rc /V %p/Inputs/parser-language-no-comma.rc 2>&1 | FileCheck %s --check-prefix PLANGUAGE1 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-language-no-comma.rc 2>&1 | FileCheck %s --check-prefix PLANGUAGE1 ; PLANGUAGE1: llvm-rc: Error parsing file: expected ',', got 7 -; RUN: not llvm-rc /V %p/Inputs/parser-language-too-many-commas.rc 2>&1 | FileCheck %s --check-prefix PLANGUAGE2 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-language-too-many-commas.rc 2>&1 | FileCheck %s --check-prefix PLANGUAGE2 ; PLANGUAGE2: llvm-rc: Error parsing file: expected '-', '~', integer or '(', got , -; RUN: not llvm-rc /V %p/Inputs/parser-html-bad-string.rc 2>&1 | FileCheck %s --check-prefix PHTML1 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-html-bad-string.rc 2>&1 | FileCheck %s --check-prefix PHTML1 ; PHTML1: llvm-rc: Error parsing file: expected string, got ThisPassesInTheOriginalToolButDocSaysItShouldBeQuoted -; RUN: not llvm-rc /V %p/Inputs/parser-html-extra-comma.rc 2>&1 | FileCheck %s --check-prefix PHTML2 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-html-extra-comma.rc 2>&1 | FileCheck %s --check-prefix PHTML2 ; PHTML2: llvm-rc: Error parsing file: expected string, got , -; RUN: not llvm-rc /V %p/Inputs/parser-accelerators-bad-flag.rc 2>&1 | FileCheck %s --check-prefix PACCELERATORS1 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-accelerators-bad-flag.rc 2>&1 | FileCheck %s --check-prefix PACCELERATORS1 ; PACCELERATORS1: llvm-rc: Error parsing file: expected ASCII/VIRTKEY/NOINVERT/ALT/SHIFT/CONTROL, got HELLO -; RUN: not llvm-rc /V %p/Inputs/parser-accelerators-bad-int-or-string.rc 2>&1 | FileCheck %s --check-prefix PACCELERATORS2 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-accelerators-bad-int-or-string.rc 2>&1 | FileCheck %s --check-prefix PACCELERATORS2 ; PACCELERATORS2: llvm-rc: Error parsing file: expected int or string, got NotIntOrString -; RUN: not llvm-rc /V %p/Inputs/parser-accelerators-no-comma.rc 2>&1 | FileCheck %s --check-prefix PACCELERATORS3 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-accelerators-no-comma.rc 2>&1 | FileCheck %s --check-prefix PACCELERATORS3 ; PACCELERATORS3: llvm-rc: Error parsing file: expected int or string, got CONTROL -; RUN: not llvm-rc /V %p/Inputs/parser-accelerators-no-comma-2.rc 2>&1 | FileCheck %s --check-prefix PACCELERATORS4 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-accelerators-no-comma-2.rc 2>&1 | FileCheck %s --check-prefix PACCELERATORS4 ; PACCELERATORS4: llvm-rc: Error parsing file: expected ',', got 10 -; RUN: not llvm-rc /V %p/Inputs/parser-menu-bad-id.rc 2>&1 | FileCheck %s --check-prefix PMENU1 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-menu-bad-id.rc 2>&1 | FileCheck %s --check-prefix PMENU1 ; PMENU1: llvm-rc: Error parsing file: expected '-', '~', integer or '(', got A -; RUN: not llvm-rc /V %p/Inputs/parser-menu-bad-flag.rc 2>&1 | FileCheck %s --check-prefix PMENU2 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-menu-bad-flag.rc 2>&1 | FileCheck %s --check-prefix PMENU2 ; PMENU2: llvm-rc: Error parsing file: expected CHECKED/GRAYED/HELP/INACTIVE/MENUBARBREAK/MENUBREAK, got ERRONEOUS -; RUN: not llvm-rc /V %p/Inputs/parser-menu-missing-block.rc 2>&1 | FileCheck %s --check-prefix PMENU3 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-menu-missing-block.rc 2>&1 | FileCheck %s --check-prefix PMENU3 ; PMENU3: llvm-rc: Error parsing file: expected '{', got POPUP -; RUN: not llvm-rc /V %p/Inputs/parser-menu-misspelled-separator.rc 2>&1 | FileCheck %s --check-prefix PMENU4 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-menu-misspelled-separator.rc 2>&1 | FileCheck %s --check-prefix PMENU4 ; PMENU4: llvm-rc: Error parsing file: expected SEPARATOR or string, got NOTSEPARATOR -; RUN: not llvm-rc /V %p/Inputs/parser-dialog-cant-give-helpid.rc 2>&1 | FileCheck %s --check-prefix PDIALOG1 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-dialog-cant-give-helpid.rc 2>&1 | FileCheck %s --check-prefix PDIALOG1 ; PDIALOG1: llvm-rc: Error parsing file: expected identifier, got , -; RUN: not llvm-rc /V %p/Inputs/parser-dialog-too-few-args.rc 2>&1 | FileCheck %s --check-prefix PDIALOG2 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-dialog-too-few-args.rc 2>&1 | FileCheck %s --check-prefix PDIALOG2 ; PDIALOG2: llvm-rc: Error parsing file: expected ',', got } -; RUN: not llvm-rc /V %p/Inputs/parser-dialog-too-many-args.rc 2>&1 | FileCheck %s --check-prefix PDIALOG3 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-dialog-too-many-args.rc 2>&1 | FileCheck %s --check-prefix PDIALOG3 ; PDIALOG3: llvm-rc: Error parsing file: expected identifier, got , -; RUN: not llvm-rc /V %p/Inputs/parser-dialog-unknown-type.rc 2>&1 | FileCheck %s --check-prefix PDIALOG4 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-dialog-unknown-type.rc 2>&1 | FileCheck %s --check-prefix PDIALOG4 ; PDIALOG4: llvm-rc: Error parsing file: expected control type, END or '}', got UNKNOWN -; RUN: not llvm-rc /V %p/Inputs/parser-dialog-unnecessary-string.rc 2>&1 | FileCheck %s --check-prefix PDIALOG5 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-dialog-unnecessary-string.rc 2>&1 | FileCheck %s --check-prefix PDIALOG5 ; PDIALOG5: llvm-rc: Error parsing file: expected '-', '~', integer or '(', got "This shouldn't be here" -; RUN: not llvm-rc /V %p/Inputs/parser-versioninfo-wrong-fixed.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO1 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-versioninfo-wrong-fixed.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO1 ; PVERSIONINFO1: llvm-rc: Error parsing file: expected fixed VERSIONINFO statement type, got WEIRDFIXED -; RUN: not llvm-rc /V %p/Inputs/parser-versioninfo-named-main-block.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO2 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-versioninfo-named-main-block.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO2 ; PVERSIONINFO2: llvm-rc: Error parsing file: expected fixed VERSIONINFO statement type, got BLOCK -; RUN: not llvm-rc /V %p/Inputs/parser-versioninfo-unnamed-inner-block.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO3 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-versioninfo-unnamed-inner-block.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO3 ; PVERSIONINFO3: llvm-rc: Error parsing file: expected string, got { -; RUN: not llvm-rc /V %p/Inputs/parser-versioninfo-unnamed-value.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO4 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-versioninfo-unnamed-value.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO4 ; PVERSIONINFO4: llvm-rc: Error parsing file: expected string, got END -; RUN: not llvm-rc /V %p/Inputs/parser-versioninfo-bad-type.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO5 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-versioninfo-bad-type.rc 2>&1 | FileCheck %s --check-prefix PVERSIONINFO5 ; PVERSIONINFO5: llvm-rc: Error parsing file: expected BLOCK or VALUE, got INCORRECT -; RUN: not llvm-rc /V %p/Inputs/parser-user-invalid-contents.rc 2>&1 | FileCheck %s --check-prefix PUSER1 +; RUN: not llvm-rc /dry-run /V %p/Inputs/parser-user-invalid-contents.rc 2>&1 | FileCheck %s --check-prefix PUSER1 ; PUSER1: llvm-rc: Error parsing file: expected int or string, got InvalidToken Index: llvm/test/tools/llvm-rc/tag-html.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/tag-html.test @@ -0,0 +1,41 @@ +; RUN: rm -rf %t && mkdir %t && cd %t +; RUN: cp %p/Inputs/webpage*.html . +; RUN: llvm-rc /FO %t/tag-html.res %p/Inputs/tag-html.rc +; RUN: llvm-readobj %t/tag-html.res | FileCheck %s --check-prefix HTML + +; HTML: Resource type (int): 23 +; HTML-NEXT: Resource name (int): 100 +; HTML-NEXT: Data version: 0 +; HTML-NEXT: Memory flags: 0x30 +; HTML-NEXT: Language ID: 1033 +; HTML-NEXT: Version (major): 0 +; HTML-NEXT: Version (minor): 0 +; HTML-NEXT: Characteristics: 0 +; HTML-NEXT: Data size: 45 +; HTML-NEXT: Data: ( +; HTML-NEXT: 0000: 3C68746D 6C3E0A20 203C626F 64793E0A |. .| +; HTML-NEXT: 0010: 20202020 48656C6C 6F210A20 203C2F62 | Hello!. ..| +; HTML-NEXT: ) + +; HTML-DAG: Resource type (int): 23 +; HTML-NEXT: Resource name (string): KITTEN +; HTML-NEXT: Data version: 0 +; HTML-NEXT: Memory flags: 0x30 +; HTML-NEXT: Language ID: 1033 +; HTML-NEXT: Version (major): 0 +; HTML-NEXT: Version (minor): 0 +; HTML-NEXT: Characteristics: 0 +; HTML-NEXT: Data size: 61 +; HTML-NEXT: Data: ( +; HTML-NEXT: 0000: 3C212D2D 2053686F 756C6420 6E6F7420 |..| +; HTML-NEXT: ) + + +; RUN: not llvm-rc /FO %t/tag-html-wrong.res %p/Inputs/tag-html-wrong.rc 2>&1 | FileCheck %s --check-prefix NOFILE + +; NOFILE: llvm-rc: Error in HTML statement (ID 1): +; NOFILE-NEXT: Error opening file 'some-really-nonexistent-file.html': Index: llvm/tools/llvm-rc/CMakeLists.txt =================================================================== --- llvm/tools/llvm-rc/CMakeLists.txt +++ llvm/tools/llvm-rc/CMakeLists.txt @@ -10,6 +10,7 @@ add_llvm_tool(llvm-rc llvm-rc.cpp + ResourceFileWriter.cpp ResourceScriptParser.cpp ResourceScriptStmt.cpp ResourceScriptToken.cpp Index: llvm/tools/llvm-rc/Opts.td =================================================================== --- llvm/tools/llvm-rc/Opts.td +++ llvm/tools/llvm-rc/Opts.td @@ -32,6 +32,9 @@ Alias, HelpText<"Display this help and exit.">; +def DRY_RUN : Flag<[ "/", "-" ], "dry-run">, + HelpText<"Don't compile the input; only try to parse it.">; + // 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. Index: llvm/tools/llvm-rc/ResourceFileWriter.h =================================================================== --- /dev/null +++ llvm/tools/llvm-rc/ResourceFileWriter.h @@ -0,0 +1,89 @@ +//===-- ResourceSerializator.h ----------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +// +// 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 { +namespace rc { + +class ResourceFileWriter : public Visitor { +public: + ResourceFileWriter(std::unique_ptr Stream) + : FS(std::move(Stream)) { + assert(FS && "Output stream needs to be provided to the serializator"); + } + + Error visitNullResource(const RCResource *) override; + Error visitHTMLResource(const RCResource *) override; + + Error visitLanguageStmt(const LanguageResource *) override; + + struct ObjectInfo { + uint16_t LanguageInfo; + + ObjectInfo() : LanguageInfo(0) {} + } ObjectData; + +private: + Error handleError(Error &&Err, const RCResource *Res); + + Error + writeResource(const RCResource *Res, + Error (ResourceFileWriter::*BodyWriter)(const RCResource *)); + + Error writeNullBody(const RCResource *); + Error writeHTMLBody(const RCResource *); + + // Output stream handling. + std::unique_ptr FS; + + uint64_t tell() const { return FS->tell(); } + + uint64_t writeObject(const ArrayRef Data); + + template uint64_t writeInt(const T &Value) { + support::detail::packed_endian_specific_integral + Object(Value); + return writeObject(Object); + } + + template uint64_t writeObject(const T &Value) { + return writeObject(ArrayRef( + reinterpret_cast(&Value), sizeof(T))); + } + + template 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); + + Error appendFile(StringRef Filename); + + void padStream(uint64_t Length); +}; + +} // namespace rc +} // namespace llvm + +#endif Index: llvm/tools/llvm-rc/ResourceFileWriter.cpp =================================================================== --- /dev/null +++ llvm/tools/llvm-rc/ResourceFileWriter.cpp @@ -0,0 +1,241 @@ +//===-- ResourceFileWriter.cpp --------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +// +// 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" + +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 { + +static Error createError(Twine Message, + std::errc Type = std::errc::invalid_argument) { + return make_error(Message, std::make_error_code(Type)); +} + +static Error checkNumberFits(uint32_t Number, size_t MaxBits, 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 +static Error checkNumberFits(uint32_t Number, Twine FieldName) { + return checkNumberFits(Number, sizeof(FitType) * 8, FieldName); +} + +static Error checkIntOrString(IntOrString Value, Twine FieldName) { + if (!Value.isInt()) + return Error::success(); + return checkNumberFits(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_lower("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; +} + +// 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. +// For now, it only strips the string boundaries, but TODO: +// * 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 &Result) { + assert(NullHandler == NullHandlingMethod::CutAtNull); + + bool IsString = stripQuotes(Str, IsLongString); + convertUTF8ToUTF16String(Str, Result); + + if (!IsString) { + // It's an identifier if it's not a string. Make all characters uppercase. + for (UTF16 &Ch : Result) { + assert(Ch <= 0x7F && "We didn't allow identifiers to be non-ASCII"); + Ch = toupper(Ch); + } + return Error::success(); + } + + // We don't process the string contents. Only cut at '\0'. + + for (size_t Pos = 0; Pos < Result.size(); ++Pos) + if (Result[Pos] == '\0') + Result.resize(Pos); + + return Error::success(); +} + +uint64_t ResourceFileWriter::writeObject(const ArrayRef Data) { + uint64_t Result = tell(); + FS->write((const char *)Data.begin(), Data.size()); + return Result; +} + +Error ResourceFileWriter::writeCString(StringRef Str, bool WriteTerminator) { + SmallVector ProcessedString; + bool IsLongString; + RETURN_IF_ERROR(processString(Str, NullHandlingMethod::CutAtNull, + IsLongString, ProcessedString)); + for (auto Ch : ProcessedString) + writeInt(Ch); + if (WriteTerminator) + writeInt(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(0xFFFF); + writeInt(Value.getInt()); + return Error::success(); +} + +Error ResourceFileWriter::appendFile(StringRef Filename) { + bool IsLong; + stripQuotes(Filename, IsLong); + + // Filename path should be relative to the current working directory. + // FIXME: docs say so, but reality is more complicated, script + // location and include paths must be taken into account. + ErrorOr> File = + MemoryBuffer::getFile(Filename, -1, false); + if (!File) + return make_error("Error opening file '" + Filename + + "': " + File.getError().message(), + File.getError()); + *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(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::visitHTMLResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeHTMLBody); +} + +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::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)); + + padStream(sizeof(uint32_t)); + object::WinResHeaderSuffix HeaderSuffix{ + ulittle32_t(0), // DataVersion; seems to always be 0 + ulittle16_t(Res->getMemoryFlags()), ulittle16_t(ObjectData.LanguageInfo), + ulittle32_t(0), // VersionInfo + ulittle32_t(0)}; // 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(); +} + +Error ResourceFileWriter::writeNullBody(const RCResource *) { + return Error::success(); +} + +Error ResourceFileWriter::writeHTMLBody(const RCResource *Base) { + return appendFile(cast(Base)->HTMLLoc); +} + +} // namespace rc +} // namespace llvm Index: llvm/tools/llvm-rc/ResourceScriptStmt.h =================================================================== --- llvm/tools/llvm-rc/ResourceScriptStmt.h +++ llvm/tools/llvm-rc/ResourceScriptStmt.h @@ -15,6 +15,7 @@ #define LLVM_TOOLS_LLVMRC_RESOURCESCRIPTSTMT_H #include "ResourceScriptToken.h" +#include "ResourceVisitor.h" #include "llvm/ADT/StringSet.h" @@ -49,15 +50,61 @@ return !IsInt && Data.String.equals_lower(Str); } + bool isInt() const { return IsInt; } + + uint32_t 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, + RkMenu = 4, + RkDialog = 5, + RkAccelerators = 9, + 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, + RkUser +}; + +// 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 { -protected: +public: IntOrString ResName; -public: RCResource() = default; RCResource(RCResource &&) = default; void setName(const IntOrString &Name) { ResName = Name; } @@ -65,6 +112,37 @@ return OS << "Base statement\n"; }; virtual ~RCResource() {} + + virtual Error visit(Visitor *) const { + llvm_unreachable("This is unable to call methods from Visitor base"); + } + + // By default, memory flags are DISCARDABLE | PURE | MOVEABLE. + virtual uint16_t getMemoryFlags() const { + 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: + 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)"; } + uint16_t getMemoryFlags() const override { return 0; } }; // Optional statement base. All such statements should derive from this base. @@ -89,12 +167,17 @@ // // Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381019(v=vs.85).aspx class LanguageResource : public OptionalStmt { +public: uint32_t Lang, SubLang; -public: 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. @@ -161,11 +244,22 @@ // // Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa966018(v=vs.85).aspx class HTMLResource : public RCResource { +public: StringRef HTMLLoc; -public: HTMLResource(StringRef Location) : 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. + uint16_t getMemoryFlags() const override { 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 -- Index: llvm/tools/llvm-rc/ResourceVisitor.h =================================================================== --- /dev/null +++ llvm/tools/llvm-rc/ResourceVisitor.h @@ -0,0 +1,39 @@ +//===-- ResourceVisitor.h ---------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +// +// 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 LanguageResource; + +class Visitor { +public: + virtual Error visitNullResource(const RCResource *) = 0; + virtual Error visitHTMLResource(const RCResource *) = 0; + + virtual Error visitLanguageStmt(const LanguageResource *) = 0; + + virtual ~Visitor() {} +}; + +} // namespace rc +} // namespace llvm + +#endif Index: llvm/tools/llvm-rc/llvm-rc.cpp =================================================================== --- llvm/tools/llvm-rc/llvm-rc.cpp +++ llvm/tools/llvm-rc/llvm-rc.cpp @@ -12,12 +12,15 @@ // //===----------------------------------------------------------------------===// -#include "ResourceScriptToken.h" +#include "ResourceFileWriter.h" #include "ResourceScriptParser.h" +#include "ResourceScriptStmt.h" +#include "ResourceScriptToken.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Support/Error.h" +#include "llvm/Support/FileSystem.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/PrettyStackTrace.h" #include "llvm/Support/Process.h" @@ -27,6 +30,7 @@ #include using namespace llvm; +using namespace llvm::rc; namespace { @@ -134,11 +138,36 @@ } } + std::unique_ptr Visitor; + bool IsDryRun = InputArgs.hasArg(OPT_DRY_RUN); + + if (!IsDryRun) { + auto OutArgsInfo = InputArgs.getAllArgValues(OPT_FILEOUT); + if (OutArgsInfo.size() != 1) + fatalError( + "Exactly one output file should be provided (using /FO flag)."); + + std::error_code EC; + auto FOut = + llvm::make_unique(OutArgsInfo[0], EC, sys::fs::F_RW); + if (EC) + fatalError("Error opening output file '" + OutArgsInfo[0] + + "': " + EC.message()); + Visitor = make_unique(std::move(FOut)); + + ExitOnErr(NullResource().visit(Visitor.get())); + + // Set the default language; choose en-US arbitrarily. + ExitOnErr(LanguageResource(0x09, 0x01).visit(Visitor.get())); + } + rc::RCParser Parser{std::move(Tokens)}; while (!Parser.isEof()) { auto Resource = ExitOnErr(Parser.parseSingleResource()); if (BeVerbose) Resource->log(outs()); + if (!IsDryRun) + ExitOnErr(Resource->visit(Visitor.get())); } return 0;