https://git.reactos.org/?p=reactos.git;a=commitdiff;h=17e094cd34648d48d3804f9419b78718fb2c207e

commit 17e094cd34648d48d3804f9419b78718fb2c207e
Author:     Hermès Bélusca-Maïto <[email protected]>
AuthorDate: Sun Jul 19 21:30:50 2020 +0200
Commit:     Hermès Bélusca-Maïto <[email protected]>
CommitDate: Wed Sep 23 00:22:48 2020 +0200

    [CMD] SET: Diverse fixes for the arithmetic-expression parser (/A option).
    
    - Detect whether a division by zero is done, and fail if so.
    
    - Detect whether an invalid number is provided:
      * If _tcstol() fails with errno == ERANGE, we've got an overflow or
        underflow.
      * If the next character where _tcstol() is not a whitespace but is a
        character compatible with the first character of an identifier, the
        number is invalid.
    
    - Add + to the list of existing unary operators (!,~,-), and parse them
      where many of these are present. Indeed, expressions like: +3, -+-+3,
      !!-+3 (or with other unary ops, etc.) are valid.
    
    - Operators constituted of more than one characters, can contain
      whitespace separating their constituting characters.
      Thus, "a + = 3" is equivalent to "a += 3" (and the same for -=, *=,
      /=, %=, &=, |= and ^=), and "a < < 3" is equivalent to "a << 3" (and
      the same for >>, <<= and >>=).
    
    - After evaluating everything, if unparsed data remains, fail and bail out.
    
    - Return Windows' CMD-compatible errorlevels.
    
    See https://ss64.com/nt/set.html for more details.
    
    Fixes some cmd_winetests.
---
 base/shell/cmd/set.c | 171 +++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 147 insertions(+), 24 deletions(-)

diff --git a/base/shell/cmd/set.c b/base/shell/cmd/set.c
index beded9515fc..1adf091ad84 100644
--- a/base/shell/cmd/set.c
+++ b/base/shell/cmd/set.c
@@ -112,14 +112,22 @@ INT cmd_set(LPTSTR param)
     if (!_tcsnicmp(param, _T("/A"), 2))
     {
         BOOL Success;
+
+        /* Save error level since seta_eval() modifies it, as
+         * we need to set it later according to specific rules. */
+        INT nOldErrorLevel = nErrorLevel;
+
         StripQuotes(param);
         Success = seta_eval(skip_ws(param + 2));
         if (!Success)
         {
+#if 0
             /* Might seem random but this is what windows xp does -- This is a 
message ID */
             retval = 9165;
+#endif
+            retval = nErrorLevel;
+            nErrorLevel = nOldErrorLevel;
         }
-        // return !Success;
         else
         {
             retval = 0;
@@ -287,12 +295,33 @@ calc(INT* lval, TCHAR op, INT rval)
     case '*':
         *lval *= rval;
         break;
+
     case '/':
+    {
+        if (rval == 0)
+        {
+            // FIXME: Localize
+            ConErrPuts(_T("Division by zero error.\n"));
+            nErrorLevel = 0x400023D1; // 1073750993;
+            return FALSE;
+        }
         *lval /= rval;
         break;
+    }
+
     case '%':
+    {
+        if (rval == 0)
+        {
+            // FIXME: Localize
+            ConErrPuts(_T("Division by zero error.\n"));
+            nErrorLevel = 0x400023D1; // 1073750993;
+            return FALSE;
+        }
         *lval %= rval;
         break;
+    }
+
     case '+':
         *lval += rval;
         break;
@@ -308,8 +337,10 @@ calc(INT* lval, TCHAR op, INT rval)
     case '|':
         *lval |= rval;
         break;
+
     default:
         ConErrResPuts(STRING_INVALID_OPERAND);
+        nErrorLevel = 0x400023CE; // 1073750990;
         return FALSE;
     }
     return TRUE;
@@ -322,23 +353,47 @@ static BOOL
 seta_unaryTerm(LPCTSTR* p_, INT* result)
 {
     LPCTSTR p = *p_;
+    INT rval;
 
     if (*p == _T('('))
     {
-        INT rval;
         p = skip_ws(p + 1);
         if (!seta_stmt(&p, &rval))
             return FALSE;
         if (*p++ != _T(')'))
         {
             ConErrResPuts(STRING_EXPECTED_CLOSE_PAREN);
+            nErrorLevel = 0x400023CC; // 1073750988;
             return FALSE;
         }
         *result = rval;
     }
-    else if (isdigit(*p))
+    else if (_istdigit(*p))
     {
-        *result = _tcstol(p, (LPTSTR*)&p, 0);
+        errno = 0;
+        rval = _tcstol(p, (LPTSTR*)&p, 0);
+
+        /* Check for overflow / underflow */
+        if (errno == ERANGE)
+        {
+            // FIXME: Localize
+            ConErrPuts(_T("Invalid number. Numbers are limited to 32-bits of 
precision.\n"));
+            nErrorLevel = 0x400023D0; // 1073750992;
+            return FALSE;
+        }
+        /*
+         * _tcstol() stopped at the first non-digit character. If it's not a 
whitespace,
+         * or if it's the start of a possible identifier, this means the 
number being
+         * interpreted was invalid.
+         */
+        else if (*p && !_istspace(*p) && __iscsymf(*p))
+        {
+            // FIXME: Localize
+            ConErrPuts(_T("Invalid number. Numeric constants are either 
decimal (42), hexadecimal (0x2A), or octal (052).\n"));
+            nErrorLevel = 0x400023CF; // 1073750991;
+            return FALSE;
+        }
+        *result = rval;
     }
     else if (__iscsymf(*p))
     {
@@ -350,6 +405,7 @@ seta_unaryTerm(LPCTSTR* p_, INT* result)
     else
     {
         ConErrResPuts(STRING_EXPECTED_NUMBER_OR_VARIABLE);
+        nErrorLevel = 0x400023CD; // 1073750989;
         return FALSE;
     }
     *p_ = skip_ws(p);
@@ -363,24 +419,36 @@ seta_mulTerm(LPCTSTR* p_, INT* result)
     TCHAR op = 0;
     INT rval;
 
-    if (_tcschr(_T("!~-"), *p))
+    if (_tcschr(_T("!~-+"), *p))
     {
         op = *p;
         p = skip_ws(p + 1);
+
+        if (!seta_mulTerm(&p, &rval))
+            return FALSE;
+
+        switch (op)
+        {
+        case '!':
+            rval = !rval;
+            break;
+        case '~':
+            rval = ~rval;
+            break;
+        case '-':
+            rval = -rval;
+            break;
+#if 0
+        case '+':
+            rval = rval;
+            break;
+#endif
+        }
     }
-    if (!seta_unaryTerm(&p, &rval))
-        return FALSE;
-    switch (op)
+    else
     {
-    case '!':
-        rval = !rval;
-        break;
-    case '~':
-        rval = ~rval;
-        break;
-    case '-':
-        rval = -rval;
-        break;
+        if (!seta_unaryTerm(&p, &rval))
+            return FALSE;
     }
 
     *result = rval;
@@ -442,12 +510,18 @@ seta_bitAndTerm(LPCTSTR* p_, INT* result)
         return FALSE;
 
     /* Handle << >> operators */
-    while (*p && _tcschr(_T("<>"), *p) && p[0] == p[1])
+    while (*p && _tcschr(_T("<>"), *p))
     {
         INT rval;
         TCHAR op = *p;
 
-        p = skip_ws(p + 2);
+        /* Check whether the next non-whitespace character is the same 
operator */
+        p = skip_ws(p + 1);
+        if (*p != op)
+            break;
+
+        /* Skip it */
+        p = skip_ws(p + 1);
 
         /* Evaluate the immediate right-hand side */
         if (!seta_logShiftTerm(&p, &rval))
@@ -473,6 +547,7 @@ seta_bitAndTerm(LPCTSTR* p_, INT* result)
 
         default:
             ConErrResPuts(STRING_INVALID_OPERAND);
+            nErrorLevel = 0x400023CE; // 1073750990;
             return FALSE;
         }
     }
@@ -512,17 +587,56 @@ seta_assignment(LPCTSTR* p_, INT* result)
     if (identlen)
     {
         p = skip_ws(p);
+
         /* Handle = assignment */
         if (*p == _T('='))
-            op = *p, p = skip_ws(p + 1);
+        {
+            op = *p;
+            p = skip_ws(p + 1);
+        }
         /* Handle *= /= %= += -= &= ^= |= assignments */
-        else if (_tcschr(_T("*/%+-&^|"), *p) && p[1] == _T('='))
-            op = *p, p = skip_ws(p + 2);
+        else if (_tcschr(_T("*/%+-&^|"), *p))
+        {
+            op = *p;
+
+            /* Find the '=', there may be some spaces before it */
+            p = skip_ws(p + 1);
+            if (*p != _T('='))
+            {
+                op = 0;
+                goto evaluate;
+            }
+
+            /* Skip it */
+            p = skip_ws(p + 1);
+        }
         /* Handle <<= >>= assignments */
-        else if (_tcschr(_T("<>"), *p) && *p == p[1] && p[2] == _T('='))
-            op = *p, p = skip_ws(p + 3);
+        else if (_tcschr(_T("<>"), *p))
+        {
+            op = *p;
+
+            /* Check whether the next non-whitespace character is the same 
operator */
+            p = skip_ws(p + 1);
+            if (*p != op)
+            {
+                op = 0;
+                goto evaluate;
+            }
+
+            /* Find the '=', there may be some spaces before it */
+            p = skip_ws(p + 1);
+            if (*p != _T('='))
+            {
+                op = 0;
+                goto evaluate;
+            }
+
+            /* Skip it */
+            p = skip_ws(p + 1);
+        }
     }
 
+evaluate:
     /* Allow to chain multiple assignments, such as: a=b=1 */
     if (ident && op)
     {
@@ -613,11 +727,20 @@ seta_eval(LPCTSTR p)
     if (!*p)
     {
         ConErrResPuts(STRING_SYNTAX_COMMAND_INCORRECT);
+        nErrorLevel = 1;
         return FALSE;
     }
     if (!seta_stmt(&p, &rval))
         return FALSE;
 
+    /* If unparsed data remains, fail and bail out */
+    if (*p)
+    {
+        ConErrResPuts(STRING_SYNTAX_COMMAND_INCORRECT); // Actually syntax 
error / missing operand.
+        nErrorLevel = 0x400023CE; // 1073750990;
+        return FALSE;
+    }
+
     /* Echo the result of the evaluation only in interactive (non-batch) mode 
*/
     if (!bc)
         ConOutPrintf(_T("%i"), rval);

Reply via email to