Index: llvm/test/tools/llvm-rc/Inputs/tag-dialog-ctl-large-coord-neg.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-dialog-ctl-large-coord-neg.rc @@ -0,0 +1,3 @@ +1 DIALOG 1, 1, 1, 1 { + LTEXT "u", 1, 5, -32769, 5, 5 +} Index: llvm/test/tools/llvm-rc/Inputs/tag-dialog-ctl-large-coord.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-dialog-ctl-large-coord.rc @@ -0,0 +1,3 @@ +1 DIALOGEX 1, 1, 1, 1 { + LTEXT "a", 1, 44444, 5, 6, 7 +} Index: llvm/test/tools/llvm-rc/Inputs/tag-dialog-ctl-large-id.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-dialog-ctl-large-id.rc @@ -0,0 +1,3 @@ +5 DIALOG 1, 2, 3, 4 { + RTEXT "Too large ID", 100000, 1, 2, 3, 4 +} Index: llvm/test/tools/llvm-rc/Inputs/tag-dialog-ctl-large-ref-id.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-dialog-ctl-large-ref-id.rc @@ -0,0 +1,3 @@ +1 DIALOGEX 1, 2, 3, 4 { + CTEXT 65536, 42, 1, 1, 1, 1 +} Index: llvm/test/tools/llvm-rc/Inputs/tag-dialog-ctl-large-size.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-dialog-ctl-large-size.rc @@ -0,0 +1,3 @@ +1 DIALOGEX 1, 2, 3, 4 { + LTEXT "L", 1, 15, 15, 40000, 15 +} Index: llvm/test/tools/llvm-rc/Inputs/tag-dialog-ctl-negative-size.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-dialog-ctl-negative-size.rc @@ -0,0 +1,3 @@ +1 DIALOG 1, 1, 1, 1 { + LTEXT "u", 1, 5, 5, 5, -700 +} Index: llvm/test/tools/llvm-rc/Inputs/tag-dialog-large-coord-neg.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-dialog-large-coord-neg.rc @@ -0,0 +1 @@ +1 DIALOG 1, -40000, 14, 15 {} Index: llvm/test/tools/llvm-rc/Inputs/tag-dialog-large-coord.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-dialog-large-coord.rc @@ -0,0 +1 @@ +1 DIALOGEX 50000, 654321, 100, 100 {} Index: llvm/test/tools/llvm-rc/Inputs/tag-dialog-large-size.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-dialog-large-size.rc @@ -0,0 +1 @@ +1 DIALOGEX 100, 100, 12345, 32768 {} Index: llvm/test/tools/llvm-rc/Inputs/tag-dialog-negative-size.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-dialog-negative-size.rc @@ -0,0 +1 @@ +1 DIALOGEX 100, 100, -50, 13 {} Index: llvm/test/tools/llvm-rc/Inputs/tag-dialog.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-dialog.rc @@ -0,0 +1,44 @@ +Empty DIALOGEX 2, 3, 4, 5 {} + +Args DIALOGEX 2, 3, 4, 5 { + LTEXT "Left text", 1, 0, 0, 50, 10 + RTEXT "Right text", 2, 12, 0, 50, 10, 42 + LTEXT "Left text 2", 3, 24, 0, 50, 10, 0xBADCAFE, 0xBAD00BAD + RTEXT "Right text 2", 4, 36, 0, 50, 10, 1, 2, 0x12345678 + + EDITTEXT 16, 100, 0, 60, 10 + EDITTEXT 17, 100, 16, 60, 10, 0xAABB0000 + EDITTEXT 18, 100, 32, 60, 10, 0xA000000B, 0xCC0000DD + EDITTEXT 19, 100, 32, 60, 10, 0, 0, 3456789012 + + PUSHBUTTON "Push 1", 32, 200, 0, 54, 11 + PUSHBUTTON "Push 2", 33, 201, 15, 54, 11, 12345 + PUSHBUTTON "Push 3", 34, 202, 30, 54, 11, 0xA, 0xC0000042 + PUSHBUTTON "Push 4", 35, 200, 45, 54, 11, 0, 1, 2 +} + +Types DIALOGEX 12345, -11215, 0x1234, 0x1EED, 0x51525354 { + LTEXT "L", 1, 2, 3, 4, 5 + CTEXT "C", 6, 7, 8, 9, 10 + RTEXT "R", 11, 12, 13, 14, 15 + + PUSHBUTTON "PB", 1001, 1002, 1003, 1004, 1005 + DEFPUSHBUTTON "DPB", 1006, 1007, 1008, 1009, 1010 + + EDITTEXT 2001, 2002, 2003, 2004, 2005 + + LTEXT 65535, 3001, 3002, 3003, 3004, 3005 +} + +EmptyOld DIALOG 1, 2, 3, 4 {} + +ArgsOld DIALOG 1, 2, 3, 4 { + LTEXT "L", 1, 2, 3, 4, 5 + LTEXT "L2", 6, 7, 8, 9, 10, 11 + LTEXT "L3", 12, 13, 14, 15, 16, 17, 18 + + EDITTEXT 19, 20, 21, 22, 23 + EDITTEXT 24, 25, 26, 27, 28, 29 + EDITTEXT 30, 31, 32, 33, 34, 35, 36 +} + Index: llvm/test/tools/llvm-rc/tag-dialog.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/tag-dialog.test @@ -0,0 +1,68 @@ +; RUN: llvm-rc /FO %t %p/Inputs/tag-dialog.rc +; RUN: diff %t %p/Inputs/tag-dialog.res + + +; RUN: not llvm-rc /FO %t %p/Inputs/tag-dialog-large-coord.rc 2>&1 | FileCheck %s --check-prefix COORD1 + +; COORD1: llvm-rc: Error in DIALOGEX statement (ID 1): +; COORD1-NEXT: Dialog x-coordinate (50000) does not fit in 16-bit signed integer type. + + +; RUN: not llvm-rc /FO %t %p/Inputs/tag-dialog-large-coord-neg.rc 2>&1 | FileCheck %s --check-prefix COORD2 + +; COORD2: llvm-rc: Error in DIALOG statement (ID 1): +; COORD2-NEXT: Dialog y-coordinate (-40000) does not fit in 16-bit signed integer type. + + +; RUN: not llvm-rc /FO %t %p/Inputs/tag-dialog-large-size.rc 2>&1 | FileCheck %s --check-prefix COORD3 + +; COORD3: llvm-rc: Error in DIALOGEX statement (ID 1): +; COORD3-NEXT: Dialog height (32768) does not fit in 16-bit signed integer type. + + +; RUN: not llvm-rc /FO %t %p/Inputs/tag-dialog-negative-size.rc 2>&1 | FileCheck %s --check-prefix COORD4 + +; COORD4: llvm-rc: Error in DIALOGEX statement (ID 1): +; COORD4-NEXT: Dialog width (-50) cannot be negative. + + +; RUN: not llvm-rc /FO %t %p/Inputs/tag-dialog-ctl-large-coord.rc 2>&1 | FileCheck %s --check-prefix CTL-COORD1 + +; CTL-COORD1: llvm-rc: Error in DIALOGEX statement (ID 1): +; CTL-COORD1-NEXT: Error in LTEXT control (ID 1): +; CTL-COORD1-NEXT: Dialog control x-coordinate (44444) does not fit in 16-bit signed integer type. + + +; RUN: not llvm-rc /FO %t %p/Inputs/tag-dialog-ctl-large-coord-neg.rc 2>&1 | FileCheck %s --check-prefix CTL-COORD2 + +; CTL-COORD2: llvm-rc: Error in DIALOG statement (ID 1): +; CTL-COORD2-NEXT: Error in LTEXT control (ID 1): +; CTL-COORD2-NEXT: Dialog control y-coordinate (-32769) does not fit in 16-bit signed integer type. + + +; RUN: not llvm-rc /FO %t %p/Inputs/tag-dialog-ctl-large-size.rc 2>&1 | FileCheck %s --check-prefix CTL-COORD3 + +; CTL-COORD3: llvm-rc: Error in DIALOGEX statement (ID 1): +; CTL-COORD3-NEXT: Error in LTEXT control (ID 1): +; CTL-COORD3-NEXT: Dialog control width (40000) does not fit in 16-bit signed integer type. + + +; RUN: not llvm-rc /FO %t %p/Inputs/tag-dialog-ctl-negative-size.rc 2>&1 | FileCheck %s --check-prefix CTL-COORD4 + +; CTL-COORD4: llvm-rc: Error in DIALOG statement (ID 1): +; CTL-COORD4-NEXT: Error in LTEXT control (ID 1): +; CTL-COORD4-NEXT: Dialog control height (-700) cannot be negative. + + +; RUN: not llvm-rc /FO %t %p/Inputs/tag-dialog-ctl-large-id.rc 2>&1 | FileCheck %s --check-prefix CTL-ID + +; CTL-ID: llvm-rc: Error in DIALOG statement (ID 5): +; CTL-ID-NEXT: Error in RTEXT control (ID 100000): +; CTL-ID-NEXT: Control ID in simple DIALOG resource (100000) does not fit in 16 bits. + + +; RUN: not llvm-rc /FO %t %p/Inputs/tag-dialog-ctl-large-ref-id.rc 2>&1 | FileCheck %s --check-prefix CTL-REF-ID + +; CTL-REF-ID: llvm-rc: Error in DIALOGEX statement (ID 1): +; CTL-REF-ID-NEXT: Error in CTEXT control (ID 42): +; CTL-REF-ID-NEXT: Control reference ID (65536) does not fit in 16 bits. Index: llvm/tools/llvm-rc/ResourceScriptParser.cpp =================================================================== --- llvm/tools/llvm-rc/ResourceScriptParser.cpp +++ llvm/tools/llvm-rc/ResourceScriptParser.cpp @@ -457,15 +457,17 @@ // [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(); - if (Control::SupportedCtls.find(ClassUpper) == Control::SupportedCtls.end()) + auto CtlInfo = Control::SupportedCtls.find(ClassUpper); + if (CtlInfo == Control::SupportedCtls.end()) return getExpectedError("control type, END or '}'", true); // Read caption if necessary. - StringRef Caption; - if (Control::CtlsWithTitle.find(ClassUpper) != Control::CtlsWithTitle.end()) { - ASSIGN_OR_RETURN(CaptionResult, readString()); + IntOrString Caption{StringRef()}; + if (CtlInfo->getValue().HasTitle) { + ASSIGN_OR_RETURN(CaptionResult, readIntOrString()); RETURN_IF_ERROR(consumeType(Kind::Comma)); Caption = *CaptionResult; } Index: llvm/tools/llvm-rc/ResourceScriptStmt.h =================================================================== --- llvm/tools/llvm-rc/ResourceScriptStmt.h +++ llvm/tools/llvm-rc/ResourceScriptStmt.h @@ -442,21 +442,40 @@ // Single control definition. class Control { - StringRef Type, Title; +public: + StringRef Type; + IntOrString Title; uint32_t ID, X, Y, Width, Height; Optional Style, ExtStyle, HelpID; -public: - Control(StringRef CtlType, StringRef CtlTitle, uint32_t CtlID, uint32_t PosX, - uint32_t PosY, uint32_t ItemWidth, uint32_t ItemHeight, + // 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 ItemStyle, Optional ExtItemStyle, Optional CtlHelpID) : Type(CtlType), Title(CtlTitle), ID(CtlID), X(PosX), Y(PosY), Width(ItemWidth), Height(ItemHeight), Style(ItemStyle), ExtStyle(ExtItemStyle), HelpID(CtlHelpID) {} - static const StringSet<> SupportedCtls; - static const StringSet<> CtlsWithTitle; + static const StringMap SupportedCtls; raw_ostream &log(raw_ostream &) const; }; @@ -465,11 +484,11 @@ // 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 Controls; bool IsExtended; -public: DialogResource(uint32_t PosX, uint32_t PosY, uint32_t DlgWidth, uint32_t DlgHeight, uint32_t DlgHelpID, OptionalStmtList &&OptStmts, bool IsDialogEx) @@ -480,6 +499,21 @@ 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: Index: llvm/tools/llvm-rc/ResourceScriptStmt.cpp =================================================================== --- llvm/tools/llvm-rc/ResourceScriptStmt.cpp +++ llvm/tools/llvm-rc/ResourceScriptStmt.cpp @@ -120,11 +120,14 @@ return OS; } -const StringSet<> Control::SupportedCtls = { - "LTEXT", "RTEXT", "CTEXT", "PUSHBUTTON", "DEFPUSHBUTTON", "EDITTEXT"}; - -const StringSet<> Control::CtlsWithTitle = {"LTEXT", "RTEXT", "CTEXT", - "PUSHBUTTON", "DEFPUSHBUTTON"}; +const StringMap Control::SupportedCtls = { + {"LTEXT", CtlInfo{0x50020000, ClsStatic, true}}, + {"CTEXT", CtlInfo{0x50020001, ClsStatic, true}}, + {"RTEXT", CtlInfo{0x50020002, ClsStatic, true}}, + {"PUSHBUTTON", CtlInfo{0x50010000, ClsButton, true}}, + {"DEFPUSHBUTTON", CtlInfo{0x50010001, ClsButton, true}}, + {"EDITTEXT", CtlInfo{0x50810000, ClsEdit, false}}, +}; raw_ostream &Control::log(raw_ostream &OS) const { OS << " Control (" << ID << "): " << Type << ", title: " << Title Index: llvm/tools/llvm-rc/ResourceSerializator.h =================================================================== --- llvm/tools/llvm-rc/ResourceSerializator.h +++ llvm/tools/llvm-rc/ResourceSerializator.h @@ -29,6 +29,7 @@ Error visitNullResource(const RCResource *) override; Error visitAcceleratorsResource(const RCResource *) override; + Error visitDialogResource(const RCResource *) override; Error visitHTMLResource(const RCResource *) override; Error visitMenuResource(const RCResource *) override; @@ -59,6 +60,10 @@ bool IsLastItem); Error writeAcceleratorsBody(const RCResource *); + // DialogResource + Error writeSingleDialogControl(const Control &, bool IsExtended); + Error writeDialogBody(const RCResource *); + // HTMLResource Error writeHTMLBody(const RCResource *); Index: llvm/tools/llvm-rc/ResourceSerializator.cpp =================================================================== --- llvm/tools/llvm-rc/ResourceSerializator.cpp +++ llvm/tools/llvm-rc/ResourceSerializator.cpp @@ -60,6 +60,25 @@ return checkNumberFits(Number, sizeof(FitType) * 8, FieldName); } +// A similar function for signed integers. +template +static Error checkSignedNumberFits(uint32_t Number, Twine FieldName, + bool CanBeNegative) { + int32_t SignedNum = Number; + if (SignedNum < std::numeric_limits::min() || + SignedNum > std::numeric_limits::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 checkIntOrString(IntOrString Value, Twine FieldName) { if (!Value.isInt()) return Error::success(); @@ -201,6 +220,10 @@ return writeResource(Res, &ResourceSerializator::writeAcceleratorsBody); } +Error ResourceSerializator::visitDialogResource(const RCResource *Res) { + return writeResource(Res, &ResourceSerializator::writeDialogBody); +} + Error ResourceSerializator::visitHTMLResource(const RCResource *Res) { return writeResource(Res, &ResourceSerializator::writeHTMLBody); } @@ -379,6 +402,151 @@ return Error::success(); } +// --- DialogResource helpers. --- // + +Error ResourceSerializator::writeSingleDialogControl(const Control &Ctl, + bool IsExtended) { + // Each control should be aligned to DWORD. + padStream(sizeof(uint32_t)); + + auto TypeInfo = Control::SupportedCtls.lookup(Ctl.Type); + uint32_t CtlStyle = TypeInfo.Style | Ctl.Style.getValueOr(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), 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)}; + writeObject(Prefix); + } + + // Common fixed-length part. + RETURN_IF_ERROR(checkSignedNumberFits( + Ctl.X, "Dialog control x-coordinate", true)); + RETURN_IF_ERROR(checkSignedNumberFits( + Ctl.Y, "Dialog control y-coordinate", true)); + RETURN_IF_ERROR( + checkSignedNumberFits(Ctl.Width, "Dialog control width", false)); + RETURN_IF_ERROR(checkSignedNumberFits( + 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) { + RETURN_IF_ERROR(checkNumberFits( + Ctl.ID, "Control ID in simple DIALOG resource")); + writeObject(ulittle16_t(Ctl.ID)); + } else { + writeObject(ulittle32_t(Ctl.ID)); + } + + // Window class - either 0xFFFF + 16-bit integer or a string. + RETURN_IF_ERROR(writeIntOrString(IntOrString(TypeInfo.CtlClass))); + + // 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. + writeObject(0); + + return Error::success(); +} + +Error ResourceSerializator::writeDialogBody(const RCResource *Base) { + auto *Res = cast(Base); + + // Default style: WS_POPUP | WS_BORDER | WS_SYSMENU. + const uint32_t UsedStyle = 0x80880000; + + // Write DIALOG(EX) header prefix. These are pretty different. + if (!Res->IsExtended) { + struct { + ulittle32_t Style; + ulittle32_t ExtStyle; + } Prefix{ulittle32_t(UsedStyle), + ulittle32_t(0)}; // As of now, we don't keep EXSTYLE. + + writeObject(Prefix); + } else { + const uint16_t DialogExMagic = 0xFFFF; + + 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(0), ulittle32_t(UsedStyle)}; + + writeObject(Prefix); + } + + // Now, a common part. First, fixed-length fields. + RETURN_IF_ERROR(checkNumberFits(Res->Controls.size(), + "Number of dialog controls")); + RETURN_IF_ERROR( + checkSignedNumberFits(Res->X, "Dialog x-coordinate", true)); + RETURN_IF_ERROR( + checkSignedNumberFits(Res->Y, "Dialog y-coordinate", true)); + RETURN_IF_ERROR( + checkSignedNumberFits(Res->Width, "Dialog width", false)); + RETURN_IF_ERROR( + checkSignedNumberFits(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. + writeObject(0); + + // Window CLASS field. Not kept here. + writeObject(0); + + // Window title. There is no title for now, so all we output is '\0'. + writeObject(0); + + 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 ResourceSerializator::writeHTMLBody(const RCResource *Base) { Index: llvm/tools/llvm-rc/ResourceVisitor.h =================================================================== --- llvm/tools/llvm-rc/ResourceVisitor.h +++ llvm/tools/llvm-rc/ResourceVisitor.h @@ -29,6 +29,7 @@ public: virtual Error visitNullResource(const RCResource *) = 0; virtual Error visitAcceleratorsResource(const RCResource *) = 0; + virtual Error visitDialogResource(const RCResource *) = 0; virtual Error visitHTMLResource(const RCResource *) = 0; virtual Error visitMenuResource(const RCResource *) = 0;