diff --git a/llvm/docs/CommandGuide/FileCheck.rst b/llvm/docs/CommandGuide/FileCheck.rst --- a/llvm/docs/CommandGuide/FileCheck.rst +++ b/llvm/docs/CommandGuide/FileCheck.rst @@ -583,8 +583,8 @@ * ``%`` is an optional scanf-style matching format specifier to indicate what number format to match (e.g. hex number). Currently accepted - format specifier are ``%u``, ``%x`` and ``%X``. If absent, the format - specifier defaults to ``%u``. + format specifier are ``%u``, ``%d``, ``%x`` and ``%X``. If absent, the + format specifier defaults to ``%u``. * ```` is the name of the numeric variable to define to the matching value. diff --git a/llvm/include/llvm/Support/FileCheck.h b/llvm/include/llvm/Support/FileCheck.h --- a/llvm/include/llvm/Support/FileCheck.h +++ b/llvm/include/llvm/Support/FileCheck.h @@ -40,10 +40,14 @@ // Numeric substitution handling code. //===----------------------------------------------------------------------===// +class FileCheckExpressionValue; + /// Bitfield representing the format an expression value should be printed into /// for matching. Used to represent both explicit format specifiers as well as /// implicit format from using numeric variables. struct FileCheckExpressionFormat { + /// Value is a signed integer. + unsigned Signed : 1; /// Value should be printed as hex number. unsigned Hex : 1; /// Value should be printed using upper case letters, only used for hex @@ -69,25 +73,125 @@ StringRef getWildcardRegex() const; /// \returns the string representation of \p Value in the format represented - /// by this instance. - std::string getMatchingString(uint64_t Value) const; + /// by this instance, or an error if conversion to this format failed. + Expected getMatchingString(FileCheckExpressionValue Value) const; /// \returns the value corresponding to string representation \p StrVal /// according to the matching format represented by this instance or an error /// with diagnostic against \p SM if \p StrVal does not correspond to a valid /// and representable value. - Expected valueFromStringRepr(StringRef StrVal, - const SourceMgr &SM) const; + Expected + valueFromStringRepr(StringRef StrVal, const SourceMgr &SM) const; }; /// Initializer for an expression without format. -const FileCheckExpressionFormat FormatNone = {0, 0, 0, 0}; +const FileCheckExpressionFormat FormatNone = {0, 0, 0, 0, 0}; /// Initializer for an expression matched as unsigned value. -const FileCheckExpressionFormat FormatUnsigned = {0, 0, 1, 0}; +const FileCheckExpressionFormat FormatUnsigned = {0, 0, 0, 1, 0}; +/// Initializer for an expression matched as signed value. +const FileCheckExpressionFormat FormatSigned = {1, 0, 0, 1, 0}; /// Initializer for an expression matched as lower case hex value. -const FileCheckExpressionFormat FormatLowHex = {1, 0, 1, 0}; +const FileCheckExpressionFormat FormatLowHex = {0, 1, 0, 1, 0}; /// Initializer for an expression matched as capital case hex value. -const FileCheckExpressionFormat FormatCapHex = {1, 1, 1, 0}; +const FileCheckExpressionFormat FormatCapHex = {0, 1, 1, 1, 0}; + +enum ValueErrorType { ValOverflow, ValPromotion, ValStringConversion }; + +/// Class to represent an error when manipulating a value. Possible value +/// errors are overflow, promotion and string conversion errors. +class FileCheckValueError : public ErrorInfo { +private: + /// Type of value error. + enum ValueErrorType Type; + +public: + static char ID; + + FileCheckValueError(ValueErrorType Type) : Type(Type) {} + + ValueErrorType getType() const { return Type; } + + /// \returns string describing this value error. + const char *getMsg() const { + switch (Type) { + case ValOverflow: + return "overflow"; + case ValPromotion: + return "promotion"; + case ValStringConversion: + return "string conversion"; + } + } + + std::error_code convertToErrorCode() const override { + return inconvertibleErrorCode(); + } + + /// Print type of value conversion that failed. + void log(raw_ostream &OS) const override { OS << Twine(getMsg()) + " error"; } +}; + +/// Class representing a numeric value. +class FileCheckExpressionValue { +private: + union { + int64_t SignedValue; + uint64_t UnsignedValue; + }; + + /// Whether value is signed (and thus is stored in SignedValue) or not (in + /// which case it is stored in UnsignedValue). + bool Signed; + +public: + /// Constructor for a signed value. + explicit FileCheckExpressionValue(int64_t Val) + : SignedValue(Val), Signed(true) {} + + /// Constructor for an unsigned value. + explicit FileCheckExpressionValue(uint64_t Val) + : UnsignedValue(Val), Signed(false) {} + + /// Define equality to be true only if both values are valid and they have + /// the same signedness and corresponding value. + bool operator==(const FileCheckExpressionValue &other); + bool operator!=(const FileCheckExpressionValue &other) { + return !(*this == other); + } + + bool isSigned() const { return Signed; } + + /// \returns the signed value. Must only be called if value is signed in the + /// first place. + int64_t getSignedValue() const { + assert(Signed); + return SignedValue; + } + + /// \returns the unsigned value. Must only be called if value is unsigned in + /// the first place. + uint64_t getUnsignedValue() const { + assert(!Signed); + return UnsignedValue; + } + + /// Converts this value to a signed value. \returns an error if not possible + /// (original value was not within range for a signed integer). + Error convertSigned(); + + /// Converts this value to an unsigned value. \returns an error if not + /// possible (original value was not within range for an unsigned integer). + Error convertUnsigned(); + + /// Performs operation and \returns its result or an error in case of + /// overflow. + friend Expected + operator+(const FileCheckExpressionValue &lhs, + const FileCheckExpressionValue &rhs); + friend Expected + operator-(const FileCheckExpressionValue &lhs, + const FileCheckExpressionValue &rhs); +}; /// Base class representing the AST of a given expression. class FileCheckExpressionAST { @@ -96,7 +200,7 @@ /// Evaluates and \returns the value of the expression represented by this /// AST or an error if evaluation fails. - virtual Expected eval() const = 0; + virtual Expected eval() const = 0; /// \returns implicit format of this AST, FormatConflict if implicit formats /// of the AST's components conflict and FormatNone if the AST has no @@ -108,14 +212,17 @@ class FileCheckExpressionLiteral : public FileCheckExpressionAST { private: /// Actual value of the literal. - uint64_t Value; + FileCheckExpressionValue Value; public: - /// Constructor for a literal. + /// Constructor for a signed literal. + FileCheckExpressionLiteral(int64_t Val) : Value(Val) {} + + /// Constructor for an unsigned literal. FileCheckExpressionLiteral(uint64_t Val) : Value(Val) {} /// \returns the literal's value. - Expected eval() const { return Value; } + Expected eval() const { return Value; } /// \returns implicit format of this literal, therefore FormatNone. FileCheckExpressionFormat getImplicitFormat() const { return FormatNone; } @@ -187,8 +294,8 @@ /// FileCheckExpression with a null AST. FileCheckExpression *Expression; - /// Value of numeric variable, if defined, or None otherwise. - Optional Value; + /// Value of numeric variable. + Optional Value; /// Line number where this variable is defined. Used to determine whether a /// variable is defined on the same line as a given use. @@ -203,7 +310,7 @@ /// Constructor for numeric variable \p Name with a known \p Value at parse /// time (e.g. the @LINE numeric variable). - FileCheckNumericVariable(StringRef Name, uint64_t Value) + FileCheckNumericVariable(StringRef Name, FileCheckExpressionValue &Value) : Name(Name), Expression(nullptr), Value(Value), DefLineNumber(0) {} /// \returns name of this numeric variable. @@ -213,7 +320,7 @@ FileCheckExpression *getExpression() const { return Expression; } /// \returns this variable's value. - Expected eval() const; + Expected eval() const; /// \returns whether this variable's value is known at match time, when /// performing the substitutions. @@ -224,7 +331,7 @@ /// Sets value of this numeric variable if not defined. \returns whether the /// variable was already defined. - bool setValue(uint64_t Value); + bool setValue(FileCheckExpressionValue Value); /// Clears value of this numeric variable. \returns whether the variable was /// already undefined. @@ -235,7 +342,8 @@ }; /// Type of functions evaluating a given binary operation. -using binop_eval_t = uint64_t (*)(uint64_t, uint64_t); +using binop_eval_t = Expected (*)( + const FileCheckExpressionValue &, const FileCheckExpressionValue &); /// Class representing a single binary operation in the AST of an expression. class FileCheckASTBinop : public FileCheckExpressionAST { @@ -259,7 +367,7 @@ /// using EvalBinop on the result of recursively evaluating the operands. /// \returns an error if a numeric variable used is undefined, or the /// expression value otherwise. - Expected eval() const; + Expected eval() const; /// \returns implicit format of this AST, FormatConflict if implicit formats /// of the AST's components conflict and Format none if the AST has no diff --git a/llvm/lib/Support/FileCheck.cpp b/llvm/lib/Support/FileCheck.cpp --- a/llvm/lib/Support/FileCheck.cpp +++ b/llvm/lib/Support/FileCheck.cpp @@ -26,36 +26,159 @@ bool FileCheckExpressionFormat:: operator==(const FileCheckExpressionFormat &other) { - return Valid == other.Valid && Hex == other.Hex && Cap == other.Cap; + return Valid == other.Valid && Conflict == other.Conflict && + Signed == other.Signed && Hex == other.Hex && Cap == other.Cap; } StringRef FileCheckExpressionFormat::getWildcardRegex() const { assert(Valid && !Conflict && "Trying to match value with invalid format"); - if (!Hex) - return StringRef("[[:digit:]]+"); - if (Cap) - return StringRef("[[:digit:]A-F]+"); - return StringRef("[[:digit:]a-f]+"); + if (Hex) { + if (Cap) + return StringRef("[[:digit:]A-F]+"); + return StringRef("[[:digit:]a-f]+"); + } + if (Signed) + return StringRef("-?[[:digit:]]+"); + return StringRef("[[:digit:]]+"); } -std::string FileCheckExpressionFormat::getMatchingString(uint64_t Value) const { +Expected FileCheckExpressionFormat::getMatchingString( + FileCheckExpressionValue Value) const { assert(Valid && !Conflict && "Trying to match value with invalid format"); + + Error ConvError = Signed ? Value.convertSigned() : Value.convertUnsigned(); + + // Conversion error (e.g. negative value converted to unsigned). + if (ConvError) + return std::move(ConvError); + if (Hex) - return utohexstr(Value, !Cap); - return utostr(Value); + return utohexstr(Value.getUnsignedValue(), !Cap); + if (Signed) + return itostr(Value.getSignedValue()); + return utostr(Value.getUnsignedValue()); } -Expected +Expected FileCheckExpressionFormat::valueFromStringRepr(StringRef StrVal, const SourceMgr &SM) const { unsigned Radix = Hex ? 16 : 10; - uint64_t Value; + if (Signed) { + int64_t SignedValue; + + if (StrVal.getAsInteger(Radix, SignedValue)) + return FileCheckErrorDiagnostic::get(SM, StrVal, + "Unable to represent numeric value"); + + return FileCheckExpressionValue(SignedValue); + } else { + uint64_t UnsignedValue; + + if (StrVal.getAsInteger(Radix, UnsignedValue)) + return FileCheckErrorDiagnostic::get(SM, StrVal, + "Unable to represent numeric value"); + + return FileCheckExpressionValue(UnsignedValue); + } +} + +bool FileCheckExpressionValue:: +operator==(const FileCheckExpressionValue &other) { + if (Signed) + return SignedValue == other.SignedValue; + else + return UnsignedValue == other.UnsignedValue; +} + +Error FileCheckExpressionValue::convertSigned() { + if (Signed) + return Error::success(); + + if (UnsignedValue > std::numeric_limits::max()) + return make_error(ValOverflow); + + SignedValue = UnsignedValue; + Signed = true; + return Error::success(); +} + +Error FileCheckExpressionValue::convertUnsigned() { + if (!Signed) + return Error::success(); + + if (SignedValue < 0) + return make_error(ValOverflow); + + UnsignedValue = SignedValue; + Signed = false; + return Error::success(); +} - if (StrVal.getAsInteger(Radix, Value)) - return FileCheckErrorDiagnostic::get(SM, StrVal, - "Unable to represent numeric value"); +Expected llvm:: +operator+(const FileCheckExpressionValue &Op1, + const FileCheckExpressionValue &Op2) { + // Operands must have same sign. + if (Op1.Signed != Op2.Signed) + return make_error(ValPromotion); + + if (Op1.Signed) { + int64_t Val1 = Op1.SignedValue; + int64_t Val2 = Op2.SignedValue; + + // Op1 + Op2 > max int64_t. + if (Val1 > 0 && Val2 > 0 && + Val1 > (std::numeric_limits::max() - Val2)) + return make_error(ValOverflow); + + // Op1 + Op2 < min int64_t. + if (Val1 < 0 && Val2 < 0 && + Val1 < (std::numeric_limits::min() - Val2)) + return make_error(ValOverflow); + + return FileCheckExpressionValue(Val1 + Val2); + } else { + uint64_t Val1 = Op1.UnsignedValue; + uint64_t Val2 = Op2.UnsignedValue; + + // Op1 + Op2 > max uint64_t. + if (Val1 > std::numeric_limits::max() - Val2) + return make_error(ValOverflow); + + return FileCheckExpressionValue(Val1 + Val2); + } +} - return Value; +Expected llvm:: +operator-(const FileCheckExpressionValue &Op1, + const FileCheckExpressionValue &Op2) { + // Operands must have same sign. + if (Op1.Signed != Op2.Signed) + return make_error(ValPromotion); + + if (Op1.Signed) { + int64_t Val1 = Op1.SignedValue; + int64_t Val2 = Op2.SignedValue; + + // Op1 - Op2 > max int64_t. + if (Val1 > 0 && Val2 < 0 && + Val1 > (std::numeric_limits::max() + Val2)) + return make_error(ValOverflow); + + // Op1 - Op2 < min int64_t. + if (Val1 < 0 && Val2 > 0 && + Val1 < (std::numeric_limits::min() + Val2)) + return make_error(ValOverflow); + + return FileCheckExpressionValue(Val1 - Val2); + } else { + uint64_t Val1 = Op1.UnsignedValue; + uint64_t Val2 = Op2.UnsignedValue; + + // Op1 < Op2. + if (Val1 < Val2) + return make_error(ValOverflow); + return FileCheckExpressionValue(Val1 - Val2); + } } FileCheckExpression::FileCheckExpression( @@ -65,7 +188,7 @@ this->Format = Format.Valid ? Format : FormatUnsigned; } -Expected FileCheckNumericVariable::eval() const { +Expected FileCheckNumericVariable::eval() const { if (Value) return *Value; @@ -86,11 +209,12 @@ return Expression ? Expression->getEffectiveFormat() : FormatNone; } -bool FileCheckNumericVariable::setValue(uint64_t NewValue) { +bool FileCheckNumericVariable::setValue(FileCheckExpressionValue NewValue) { if (Value) return true; if (Expression != nullptr && Expression->getAST() != nullptr) { - Expected EvaluatedValue = Expression->getAST()->eval(); + Expected EvaluatedValue = + Expression->getAST()->eval(); if (!EvaluatedValue || *EvaluatedValue != NewValue) return true; } @@ -106,9 +230,9 @@ return false; } -Expected FileCheckASTBinop::eval() const { - Expected LeftOp = this->LeftOp->eval(); - Expected RightOp = this->RightOp->eval(); +Expected FileCheckASTBinop::eval() const { + Expected LeftOp = this->LeftOp->eval(); + Expected RightOp = this->RightOp->eval(); // Uses undefined variable(s). if (!LeftOp || !RightOp) { @@ -120,7 +244,19 @@ return std::move(Err); } - return EvalBinop(*LeftOp, *RightOp); + // Integer promotion. + FileCheckExpressionValue LeftValue = *LeftOp; + FileCheckExpressionValue RightValue = *RightOp; + if (!LeftValue.isSigned() || !RightValue.isSigned()) { + Error ConvError = LeftValue.convertUnsigned(); + if (ConvError) + return std::move(ConvError); + ConvError = RightValue.convertUnsigned(); + if (ConvError) + return std::move(ConvError); + } + + return EvalBinop(LeftValue, RightValue); } FileCheckExpressionFormat FileCheckASTBinop::getImplicitFormat() const { @@ -137,7 +273,8 @@ Expected FileCheckNumericSubstitution::getResult() const { assert(Expression->getAST() != nullptr && "Substituting empty expression"); - Expected EvaluatedValue = Expression->getAST()->eval(); + Expected EvaluatedValue = + Expression->getAST()->eval(); if (!EvaluatedValue) return EvaluatedValue.takeError(); FileCheckExpressionFormat Format = Expression->getEffectiveFormat(); @@ -203,6 +340,7 @@ return C; } +char FileCheckValueError::ID = 0; char FileCheckUndefVarError::ID = 0; char FileCheckErrorDiagnostic::ID = 0; char FileCheckNotFoundError::ID = 0; @@ -284,22 +422,20 @@ // Otherwise, parse it as a literal. unsigned Radix = (AO == Literal) ? 10 : 0; - uint64_t LiteralValue; - if (!Expr.consumeInteger(Radix, LiteralValue)) - return std::make_shared(LiteralValue); + int64_t SignedLiteralValue; + uint64_t UnsignedLiteralValue; + StringRef SaveExpr = Expr; + // Accept both signed and unsigned literal. + if (AO == Any && !Expr.consumeInteger(Radix, SignedLiteralValue)) + return std::make_shared(SignedLiteralValue); + Expr = SaveExpr; + if (!Expr.consumeInteger(Radix, UnsignedLiteralValue)) + return std::make_shared(UnsignedLiteralValue); return FileCheckErrorDiagnostic::get(SM, Expr, "invalid operand format '" + Expr + "'"); } -static uint64_t add(uint64_t LeftOp, uint64_t RightOp) { - return LeftOp + RightOp; -} - -static uint64_t sub(uint64_t LeftOp, uint64_t RightOp) { - return LeftOp - RightOp; -} - Expected> FileCheckPattern::parseBinop( StringRef &Expr, std::shared_ptr LeftOp, bool LegacyLineExpr, size_t LineNumber, FileCheckPatternContext *Context, @@ -315,10 +451,10 @@ binop_eval_t EvalBinop; switch (Operator) { case '+': - EvalBinop = add; + EvalBinop = operator+; break; case '-': - EvalBinop = sub; + EvalBinop = operator-; break; default: return FileCheckErrorDiagnostic::get( @@ -367,6 +503,9 @@ case 'u': ExplicitFormat = FormatUnsigned; break; + case 'd': + ExplicitFormat = FormatSigned; + break; case 'x': ExplicitFormat = FormatLowHex; break; @@ -461,8 +600,9 @@ // Create fake @LINE pseudo variable definition. StringRef LinePseudo = "@LINE"; uint64_t LineNumber64 = LineNumber; + auto LineNumberVal = FileCheckExpressionValue(LineNumber64); std::shared_ptr LinePseudoVar = - std::make_shared(LinePseudo, LineNumber64); + std::make_shared(LinePseudo, LineNumberVal); Context->GlobalNumericVariableTable[LinePseudo] = LinePseudoVar; if (!(Req.NoCanonicalizeWhiteSpace && Req.MatchFullLines)) @@ -783,8 +923,22 @@ for (const auto &Substitution : Substitutions) { // Substitute and check for failure (e.g. use of undefined variable). Expected Value = Substitution->getResult(); - if (!Value) - return Value.takeError(); + if (!Value) { + Error Err = + handleErrors(Value.takeError(), [&](const FileCheckValueError &E) { + switch (E.getType()) { + case ValOverflow: + case ValPromotion: + return FileCheckErrorDiagnostic::get( + SM, Substitution->getFromString(), + Twine("Unable to substitute variable or expression: ") + + E.getMsg()); + default: + llvm_unreachable("Unexpected value error"); + } + }); + return std::move(Err); + } // Plop it into the regex at the adjusted offset. TmpStr.insert(TmpStr.begin() + Substitution->getIndex() + InsertOffset, @@ -824,7 +978,8 @@ assert(DefinedNumericVariable->getExpression() != nullptr); FileCheckExpressionFormat Format = DefinedNumericVariable->getExpression()->getEffectiveFormat(); - Expected Value = Format.valueFromStringRepr(MatchedValue, SM); + Expected Value = + Format.valueFromStringRepr(MatchedValue, SM); if (!Value) return Value.takeError(); if (DefinedNumericVariable->setValue(*Value)) @@ -873,6 +1028,15 @@ [](const FileCheckNotFoundError &E) {}, // Handled in PrintNoMatch() [](const FileCheckErrorDiagnostic &E) {}, + [](const FileCheckValueError &E) { + switch (E.getType()) { + case ValOverflow: + case ValPromotion: + case ValStringConversion: + // handled in match() + break; + } + }, [&](const FileCheckUndefVarError &E) { if (!UndefSeen) { OS << "uses undefined variable(s):"; @@ -880,9 +1044,6 @@ } OS << " "; E.log(OS); - }, - [](const ErrorInfoBase &E) { - llvm_unreachable("Unexpected error"); }); } else { // Substitution succeeded. Print substituted value. @@ -1970,7 +2131,7 @@ continue; } FileCheckExpression *Expression = *ExpressionResult; - Expected Value = Expression->getAST()->eval(); + Expected Value = Expression->getAST()->eval(); if (!Value) { Errs = joinErrors(std::move(Errs), Value.takeError()); continue; diff --git a/llvm/test/FileCheck/numeric-expression.txt b/llvm/test/FileCheck/numeric-expression.txt --- a/llvm/test/FileCheck/numeric-expression.txt +++ b/llvm/test/FileCheck/numeric-expression.txt @@ -21,26 +21,28 @@ ; Numeric variable definition with explicit matching format. DEF FMT +-30 c D CHECK-LABEL: DEF FMT -CHECK-NEXT: [[#%x,VAR2:]] -CHECK-NEXT: [[#%X,VAR3:]] +CHECK-NEXT: [[#%d,VAR2:]] +CHECK-NEXT: [[#%x,VAR3:]] +CHECK-NEXT: [[#%X,VAR4:]] ; Numeric variable definition with explicit matching format with different ; spacing. DEF FMT SPC -c -c -c -c -c +-30 +-30 +-30 +-30 +-30 CHECK-LABEL: DEF FMT SPC -CHECK-NEXT: [[#%x, VAR2a:]] -CHECK-NEXT: [[# %x, VAR2b:]] -CHECK-NEXT: [[# %x , VAR2c:]] -CHECK-NEXT: [[# %x , VAR2d :]] -CHECK-NEXT: [[# %x , VAR2e : ]] +CHECK-NEXT: [[#%d, VAR2a:]] +CHECK-NEXT: [[# %d, VAR2b:]] +CHECK-NEXT: [[# %d , VAR2c:]] +CHECK-NEXT: [[# %d , VAR2d :]] +CHECK-NEXT: [[# %d , VAR2e : ]] ; Numeric expressions in explicit matching format and default matching rule using ; variables defined on other lines. @@ -48,42 +50,52 @@ 11 12 10 +-30 +-29 +-31 c d b 1a +1a D E C 1B +1B 11 11 11 -c -c -c -c -c +-30 +-30 +-30 +-30 +-30 CHECK-LABEL: USE DEF FMT IMPL MATCH CHECK-NEXT: [[#%u,VAR1]] CHECK-NEXT: [[#%u,VAR1+1]] CHECK-NEXT: [[#%u,VAR1-1]] -CHECK-NEXT: [[#%x,VAR2]] -CHECK-NEXT: [[#%x,VAR2+1]] -CHECK-NEXT: [[#%x,VAR2-1]] -CHECK-NEXT: [[#%x,VAR2+14]] -CHECK-NEXT: [[#%X,VAR3]] -CHECK-NEXT: [[#%X,VAR3+1]] -CHECK-NEXT: [[#%X,VAR3-1]] -CHECK-NEXT: [[#%X,VAR3+14]] +CHECK-NEXT: [[#%d,VAR2]] +CHECK-NEXT: [[#%d,VAR2+1]] +CHECK-NEXT: [[#%d,VAR2-1]] +CHECK-NEXT: [[#%x,VAR3]] +CHECK-NEXT: [[#%x,VAR3+1]] +CHECK-NEXT: [[#%x,VAR3-1]] +CHECK-NEXT: [[#%x,VAR3+0xe]] +CHECK-NEXT: [[#%x,VAR3+0xE]] +CHECK-NEXT: [[#%X,VAR4]] +CHECK-NEXT: [[#%X,VAR4+1]] +CHECK-NEXT: [[#%X,VAR4-1]] +CHECK-NEXT: [[#%X,VAR4+0xe]] +CHECK-NEXT: [[#%X,VAR4+0xE]] CHECK-NEXT: [[#%u,VAR1a]] CHECK-NEXT: [[#%u,VAR1b]] CHECK-NEXT: [[#%u,VAR1c]] -CHECK-NEXT: [[#%x,VAR2a]] -CHECK-NEXT: [[#%x,VAR2b]] -CHECK-NEXT: [[#%x,VAR2c]] -CHECK-NEXT: [[#%x,VAR2d]] -CHECK-NEXT: [[#%x,VAR2e]] +CHECK-NEXT: [[#%d,VAR2a]] +CHECK-NEXT: [[#%d,VAR2b]] +CHECK-NEXT: [[#%d,VAR2c]] +CHECK-NEXT: [[#%d,VAR2d]] +CHECK-NEXT: [[#%d,VAR2e]] ; Numeric expressions in explicit matching format and default matching rule using ; variables defined on other lines with different spacing. @@ -126,14 +138,19 @@ 11 12 10 +-30 +-29 +-31 c d b 1a +1a D E C 1B +1B CHECK-LABEL: USE IMPL FMT IMPL MATCH CHECK-NEXT: [[#VAR1]] CHECK-NEXT: [[#VAR1+1]] @@ -141,17 +158,22 @@ CHECK-NEXT: [[#VAR2]] CHECK-NEXT: [[#VAR2+1]] CHECK-NEXT: [[#VAR2-1]] -CHECK-NEXT: [[#VAR2+14]] CHECK-NEXT: [[#VAR3]] CHECK-NEXT: [[#VAR3+1]] CHECK-NEXT: [[#VAR3-1]] -CHECK-NEXT: [[#VAR3+14]] +CHECK-NEXT: [[#VAR3+0xe]] +CHECK-NEXT: [[#VAR3+0xE]] +CHECK-NEXT: [[#VAR4]] +CHECK-NEXT: [[#VAR4+1]] +CHECK-NEXT: [[#VAR4-1]] +CHECK-NEXT: [[#VAR4+0xe]] +CHECK-NEXT: [[#VAR4+0xE]] ; Explicit format override implicit format conflicts VAR USE IMPL OVERRIDE FMT CONFLICT 23 CHECK-LABEL: VAR USE IMPL OVERRIDE FMT CONFLICT -CHECK-NEXT: [[# %u, VAR1 + VAR2]] +CHECK-NEXT: [[# %u, VAR1 + VAR3]] ; Numeric expressions using variables defined on the command-line and an ; immediate interpreted as an unsigned value. @@ -167,12 +189,16 @@ b B 12 +12 +13 13 CHECK-LABEL: USE CONV FMT IMPL MATCH CHECK-NEXT: [[# %x, VAR1]] CHECK-NEXT: [[# %X, VAR1]] -CHECK-NEXT: [[# %u, VAR2]] CHECK-NEXT: [[# %u, VAR3]] +CHECK-NEXT: [[# %d, VAR3]] +CHECK-NEXT: [[# %u, VAR4]] +CHECK-NEXT: [[# %d, VAR4]] ; Numeric variable definition with unsupported matching format RUN: not FileCheck -check-prefixes ERR,INVALID-FMT-SPEC1 -input-file %s %s 2>&1 \ @@ -302,3 +328,29 @@ FMT-CONFLICT-MSG: numeric-expression.txt:[[#@LINE-1]]:23: error: variables with conflicting format specifier: need an explicit one FMT-CONFLICT-MSG-NEXT: {{F}}MT-CONFLICT-NEXT: {{\[\[#VAR1 \+ VAR2\]\]}} FMT-CONFLICT-MSG-NEXT: {{^ \^$}} + + +; Numeric expression with overflow +RUN: not FileCheck -check-prefix OVERFLOW -input-file %s %s 2>&1 \ +RUN: | FileCheck -check-prefix OVERFLOW-MSG %s + +OVERFLOW +BIGVAR=10000000000000000 +OVERFLOW-LABEL: OVERFLOW +OVERFLOW-NEXT: BIGVAR: [[#BIGVAR:0x8000000000000000+0x8000000000000000]] +OVERFLOW-MSG: numeric-expression.txt:[[#@LINE-1]]:27: error: Unable to substitute variable or numeric expression +OVERFLOW-MSG-NEXT: {{O}}VERFLOW-NEXT: BIGVAR: {{\[\[#BIGVAR:0x8000000000000000\+0x8000000000000000\]\]}} +OVERFLOW-MSG-NEXT: {{^ \^$}} + + +; Numeric expression with underflow +RUN: not FileCheck -check-prefix UNDERFLOW -input-file %s %s 2>&1 \ +RUN: | FileCheck -check-prefix UNDERFLOW-MSG %s + +UNDERFLOW +TINYVAR=-10000000000000000 +UNDERFLOW-LABEL: UNDERFLOW +UNDERFLOW-NEXT: TINYVAR: [[#%d,TINYVAR:-0x8000000000000000-0x8000000000000000]] +UNDERFLOW-MSG: numeric-expression.txt:[[#@LINE-1]]:29: error: Unable to substitute variable or numeric expression +UNDERFLOW-MSG-NEXT: {{U}}NDERFLOW-NEXT: TINYVAR: {{\[\[#%d,TINYVAR:-0x8000000000000000-0x8000000000000000\]\]}} +UNDERFLOW-MSG-NEXT: {{^ \^$}} diff --git a/llvm/unittests/Support/FileCheckTest.cpp b/llvm/unittests/Support/FileCheckTest.cpp --- a/llvm/unittests/Support/FileCheckTest.cpp +++ b/llvm/unittests/Support/FileCheckTest.cpp @@ -16,10 +16,10 @@ TEST_F(FileCheckTest, Literal) { // Eval returns the literal's value. - auto Ten = FileCheckExpressionLiteral(10); - Expected Value = Ten.eval(); + auto Ten = FileCheckExpressionLiteral((uint64_t)10); + Expected Value = Ten.eval(); EXPECT_TRUE(static_cast(Value)); - EXPECT_EQ(10U, *Value); + EXPECT_EQ(10U, Value->getUnsignedValue()); } // Return whether Err contains any FileCheckUndefVarError whose associated name @@ -38,7 +38,20 @@ return expectUndefErrors({ExpectedUndefVarName.str()}, std::move(Err)); } -uint64_t doAdd(uint64_t OpL, uint64_t OpR) { return OpL + OpR; } +Expected doAdd(const FileCheckExpressionValue &Op1, + const FileCheckExpressionValue &Op2) { + if (Op1.isSigned()) { + int64_t Val1 = Op1.getSignedValue(); + int64_t Val2 = Op2.getSignedValue(); + + return FileCheckExpressionValue(Val1 + Val2); + } else { + uint64_t Val1 = Op1.getUnsignedValue(); + uint64_t Val2 = Op2.getUnsignedValue(); + + return FileCheckExpressionValue(Val1 + Val2); + } +} TEST_F(FileCheckTest, NumericVariable) { // Undefined variable: isMatchTimeKnown returns false, eval and clearValue @@ -49,26 +62,28 @@ std::make_shared(1, "FOO", &NumVarExpr); EXPECT_EQ("FOO", FooVar->getName()); EXPECT_FALSE(FooVar->isMatchTimeKnown()); - Expected Value = FooVar->eval(); + Expected Value = FooVar->eval(); EXPECT_FALSE(Value); EXPECT_FALSE(expectUndefError("FOO", Value.takeError())); EXPECT_TRUE(FooVar->clearValue()); - EXPECT_FALSE(FooVar->setValue(42)); + FileCheckExpressionValue LifeAnswer = FileCheckExpressionValue((uint64_t)42); + EXPECT_FALSE(FooVar->setValue(LifeAnswer)); // Defined variable: isMatchTimeKnown returns true, eval returns value set // and setValue fails. EXPECT_TRUE(FooVar->isMatchTimeKnown()); Value = FooVar->eval(); EXPECT_TRUE(static_cast(Value)); - EXPECT_EQ(42U, *Value); - EXPECT_TRUE(FooVar->setValue(43)); + EXPECT_EQ(42U, Value->getUnsignedValue()); + FileCheckExpressionValue FourThree = FileCheckExpressionValue((uint64_t)43); + EXPECT_TRUE(FooVar->setValue(FourThree)); Value = FooVar->eval(); EXPECT_TRUE(static_cast(Value)); - EXPECT_EQ(42U, *Value); + EXPECT_EQ(42U, Value->getUnsignedValue()); // Undefined variable set from numeric expression: isMatchTimeKnown returns // true, eval returns value of expression, and setValue succeeds. - auto One = std::make_shared(1); + auto One = std::make_shared((uint64_t)1); auto Binop = std::make_shared(doAdd, FooVar, One); auto FoobarExpr = FileCheckExpression(Binop, FormatUnsigned); FileCheckNumericVariable FoobarVar = @@ -76,11 +91,11 @@ EXPECT_TRUE(FoobarVar.isMatchTimeKnown()); Value = FoobarVar.eval(); EXPECT_TRUE(static_cast(Value)); - EXPECT_EQ(43U, *Value); - EXPECT_FALSE(FoobarVar.setValue(43)); + EXPECT_EQ(43U, Value->getUnsignedValue()); + EXPECT_FALSE(FoobarVar.setValue(FourThree)); Value = FoobarVar.eval(); EXPECT_TRUE(static_cast(Value)); - EXPECT_EQ(43U, *Value); + EXPECT_EQ(43U, Value->getUnsignedValue()); // Clearing variable: eval fails and clearValue again fails. // appendUndefVarNames returns the variable again. @@ -100,16 +115,18 @@ FileCheckExpression(nullptr, FormatUnsigned); auto FooVar = std::make_shared(1, "FOO", &DefExpression); - FooVar->setValue(42); + FileCheckExpressionValue LifeAnswer = FileCheckExpressionValue((uint64_t)42); + FooVar->setValue(LifeAnswer); auto BarVar = std::make_shared(2, "BAR", &DefExpression); - BarVar->setValue(18); + FileCheckExpressionValue OneEight = FileCheckExpressionValue((uint64_t)18); + BarVar->setValue(OneEight); FileCheckASTBinop Binop = FileCheckASTBinop(doAdd, FooVar, BarVar); // Defined variable: eval returns right value. - Expected Value = Binop.eval(); + Expected Value = Binop.eval(); EXPECT_TRUE(static_cast(Value)); - EXPECT_EQ(60U, *Value); + EXPECT_EQ(60U, Value->getUnsignedValue()); // 1 undefined variable: eval fails, error contains name of undefined // variable. @@ -402,8 +419,10 @@ std::make_shared(1, "@LINE", &DefExpression); auto NVar = std::make_shared(1, "N", &DefExpression); - LineVar->setValue(42); - NVar->setValue(10); + FileCheckExpressionValue LifeAnswer = FileCheckExpressionValue((uint64_t)42); + LineVar->setValue(LifeAnswer); + FileCheckExpressionValue Ten = FileCheckExpressionValue((uint64_t)10); + NVar->setValue(Ten); FileCheckExpression ExpressionLine = FileCheckExpression(LineVar, FormatUnsigned); FileCheckExpression ExpressionN = FileCheckExpression(NVar, FormatUnsigned); @@ -505,9 +524,10 @@ Expected EmptyVar = Cxt.getPatternVarValue(EmptyVarStr); Expected UnknownVar = Cxt.getPatternVarValue(UnknownVarStr); EXPECT_TRUE(static_cast(Expression)); - Expected ExpressionVal = (*Expression)->getAST()->eval(); + Expected ExpressionVal = + (*Expression)->getAST()->eval(); EXPECT_TRUE(static_cast(ExpressionVal)); - EXPECT_EQ(*ExpressionVal, 18U); + EXPECT_EQ(ExpressionVal->getSignedValue(), 18); EXPECT_TRUE(static_cast(EmptyVar)); EXPECT_EQ(*EmptyVar, ""); EXPECT_TRUE(errorToBool(UnknownVar.takeError())); @@ -545,7 +565,7 @@ EXPECT_TRUE(static_cast(Expression)); ExpressionVal = (*Expression)->getAST()->eval(); EXPECT_TRUE(static_cast(ExpressionVal)); - EXPECT_EQ(*ExpressionVal, 36U); + EXPECT_EQ(ExpressionVal->getSignedValue(), 36); // Clear local variables and check global variables remain defined. Cxt.clearLocalVars(); @@ -557,6 +577,6 @@ EXPECT_TRUE(static_cast(Expression)); ExpressionVal = (*Expression)->getAST()->eval(); EXPECT_TRUE(static_cast(ExpressionVal)); - EXPECT_EQ(*ExpressionVal, 36U); + EXPECT_EQ(ExpressionVal->getSignedValue(), 36); } } // namespace