diff --git a/test/tools/llvm-rc/Inputs/parser-expr-bad-binary-1.rc b/test/tools/llvm-rc/Inputs/parser-expr-bad-binary-1.rc new file mode 100644 index 00000000000..9980a125624 Binary files /dev/null and b/test/tools/llvm-rc/Inputs/parser-expr-bad-binary-1.rc differ diff --git a/test/tools/llvm-rc/Inputs/parser-expr-bad-binary-2.rc b/test/tools/llvm-rc/Inputs/parser-expr-bad-binary-2.rc new file mode 100644 index 00000000000..0d406d06fa4 Binary files /dev/null and b/test/tools/llvm-rc/Inputs/parser-expr-bad-binary-2.rc differ diff --git a/test/tools/llvm-rc/Inputs/parser-expr-bad-binary-3.rc b/test/tools/llvm-rc/Inputs/parser-expr-bad-binary-3.rc new file mode 100644 index 00000000000..89cb1cbd5f9 Binary files /dev/null and b/test/tools/llvm-rc/Inputs/parser-expr-bad-binary-3.rc differ diff --git a/test/tools/llvm-rc/Inputs/parser-expr-bad-unary.rc b/test/tools/llvm-rc/Inputs/parser-expr-bad-unary.rc new file mode 100644 index 00000000000..c92538f47a3 Binary files /dev/null and b/test/tools/llvm-rc/Inputs/parser-expr-bad-unary.rc differ diff --git a/test/tools/llvm-rc/Inputs/parser-expr-unbalanced-1.rc b/test/tools/llvm-rc/Inputs/parser-expr-unbalanced-1.rc new file mode 100644 index 00000000000..27fbfa8b2a2 Binary files /dev/null and b/test/tools/llvm-rc/Inputs/parser-expr-unbalanced-1.rc differ diff --git a/test/tools/llvm-rc/Inputs/parser-expr-unbalanced-2.rc b/test/tools/llvm-rc/Inputs/parser-expr-unbalanced-2.rc new file mode 100644 index 00000000000..46df9ded35a Binary files /dev/null and b/test/tools/llvm-rc/Inputs/parser-expr-unbalanced-2.rc differ diff --git a/test/tools/llvm-rc/Inputs/parser-expr-unbalanced-3.rc b/test/tools/llvm-rc/Inputs/parser-expr-unbalanced-3.rc new file mode 100644 index 00000000000..09e5d5a0991 Binary files /dev/null and b/test/tools/llvm-rc/Inputs/parser-expr-unbalanced-3.rc differ diff --git a/test/tools/llvm-rc/Inputs/parser-expr.rc b/test/tools/llvm-rc/Inputs/parser-expr.rc new file mode 100644 index 00000000000..8e69c1cd1fa Binary files /dev/null and b/test/tools/llvm-rc/Inputs/parser-expr.rc differ diff --git a/test/tools/llvm-rc/parser-expr.test b/test/tools/llvm-rc/parser-expr.test new file mode 100644 index 00000000000..e184fd7bffd --- /dev/null +++ b/test/tools/llvm-rc/parser-expr.test @@ -0,0 +1,52 @@ +; RUN: llvm-rc /V %p/Inputs/parser-expr.rc | FileCheck %s + +; CHECK: Language: 5, Sublanguage: 1 +; CHECK-NEXT: Language: 3, Sublanguage: 2 +; CHECK-NEXT: Language: 4294967293, Sublanguage: 4294967292 +; CHECK-NEXT: Language: 0, Sublanguage: 1 +; CHECK-NEXT: Language: 2, Sublanguage: 4 +; CHECK-NEXT: Language: 3, Sublanguage: 5 +; CHECK-NEXT: Language: 2, Sublanguage: 0 +; CHECK-NEXT: Language: 4294967295, Sublanguage: 1 +; CHECK-NEXT: Language: 1, Sublanguage: 4294967295 +; CHECK-NEXT: Language: 4294967294, Sublanguage: 1 +; CHECK-NEXT: Language: 1, Sublanguage: 4294967294 +; CHECK-NEXT: Language: 2, Sublanguage: 1 +; CHECK-NEXT: Language: 3, Sublanguage: 5 +; CHECK-NEXT: Language: 0, Sublanguage: 13 +; 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 + +; 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 + +; 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 + +; 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 + +; 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 + +; 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 + +; 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 + +; UNBALANCED3: llvm-rc: Error parsing file: expected ',', got ) diff --git a/test/tools/llvm-rc/parser.test b/test/tools/llvm-rc/parser.test index 1f11a853df2..e1477a822a3 100644 --- a/test/tools/llvm-rc/parser.test +++ b/test/tools/llvm-rc/parser.test @@ -106,12 +106,12 @@ ; RUN: not llvm-rc /V %p/Inputs/parser-eof.rc 2>&1 | FileCheck %s --check-prefix PEOF -; PEOF: llvm-rc: Error parsing file: expected integer, got +; 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 -; PCHARACTERISTICS1: llvm-rc: Error parsing file: expected integer, got BEGIN +; 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 @@ -136,7 +136,7 @@ ; RUN: not llvm-rc /V %p/Inputs/parser-language-too-many-commas.rc 2>&1 | FileCheck %s --check-prefix PLANGUAGE2 -; PLANGUAGE2: llvm-rc: Error parsing file: expected integer, got , +; 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 @@ -171,7 +171,7 @@ ; RUN: not llvm-rc /V %p/Inputs/parser-menu-bad-id.rc 2>&1 | FileCheck %s --check-prefix PMENU1 -; PMENU1: llvm-rc: Error parsing file: expected integer, got A +; 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 @@ -211,7 +211,7 @@ ; RUN: not llvm-rc /V %p/Inputs/parser-dialog-unnecessary-string.rc 2>&1 | FileCheck %s --check-prefix PDIALOG5 -; PDIALOG5: llvm-rc: Error parsing file: expected integer, got "This shouldn't be here" +; 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 diff --git a/tools/llvm-rc/ResourceScriptParser.cpp b/tools/llvm-rc/ResourceScriptParser.cpp index 854b9202d96..ee7de6b937a 100644 --- a/tools/llvm-rc/ResourceScriptParser.cpp +++ b/tools/llvm-rc/ResourceScriptParser.cpp @@ -107,10 +107,102 @@ void RCParser::consume() { CurLoc++; } -Expected RCParser::readInt() { - if (!isNextTokenKind(Kind::Int)) - return getExpectedError("integer"); - return read().intValue(); +// An integer description might consist of a single integer or +// an arithmetic expression evaluating to the integer. The expressions +// can contain the following tokens: ( ) + - | & ~. Their meaning +// is the same as in C++. +// The operators in the original RC implementation have the following +// precedence: +// 1) Unary operators (- ~), +// 2) Binary operators (+ - & |), with no precedence. +// +// The following grammar is used to parse the expressions Exp1: +// Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2 +// Exp2 ::= -Exp2 || ~Exp2 || 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 RCParser::readInt() { return parseIntExpr1(); } + +Expected RCParser::parseIntExpr1() { + // Exp1 ::= Exp2 || Exp1 + Exp2 || Exp1 - Exp2 || Exp1 | Exp2 || Exp1 & Exp2. + ASSIGN_OR_RETURN(FirstResult, parseIntExpr2()); + uint32_t 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 RCParser::parseIntExpr2() { + // Exp2 ::= -Exp2 || ~Exp2 || 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 read().intValue(); + + case Kind::LeftParen: { + consume(); + ASSIGN_OR_RETURN(Result, parseIntExpr1()); + RETURN_IF_ERROR(consumeType(Kind::RightParen)); + return *Result; + } + + default: + return getExpectedError(ErrorMsg); + } } Expected RCParser::readString() { diff --git a/tools/llvm-rc/ResourceScriptParser.h b/tools/llvm-rc/ResourceScriptParser.h index 132106f0c4e..56042f79d5f 100644 --- a/tools/llvm-rc/ResourceScriptParser.h +++ b/tools/llvm-rc/ResourceScriptParser.h @@ -77,12 +77,18 @@ private: // 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 readInt(); // Parse an integer. Expected readString(); // Parse a string. Expected readIdentifier(); // Parse an identifier. Expected readIntOrString(); // Parse an integer or a string. Expected readTypeOrName(); // Parse an integer or an identifier. + // Helper integer expression parsing methods. + Expected parseIntExpr1(); + Expected parseIntExpr2(); + // Advance the state by one, discarding the current token. // If the discarded token had an incorrect type, fail. Error consumeType(Kind TokenKind); diff --git a/tools/llvm-rc/ResourceScriptToken.cpp b/tools/llvm-rc/ResourceScriptToken.cpp index ba1ed5d416a..df9e3d9caab 100644 --- a/tools/llvm-rc/ResourceScriptToken.cpp +++ b/tools/llvm-rc/ResourceScriptToken.cpp @@ -60,6 +60,18 @@ 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("Error parsing file: " + message, inconvertibleErrorCode()); diff --git a/tools/llvm-rc/ResourceScriptToken.h b/tools/llvm-rc/ResourceScriptToken.h index 268f37a9d00..6846e096848 100644 --- a/tools/llvm-rc/ResourceScriptToken.h +++ b/tools/llvm-rc/ResourceScriptToken.h @@ -60,6 +60,9 @@ public: StringRef value() const; Kind kind() const; + // Check if a token describes a binary operator. + bool isBinaryOp() const; + private: Kind TokenKind; StringRef TokenValue;