Fix CFXJSE_FormCalcContext::Substr() behavior.

- Update tests according to the XFA spec.
- Make it behave as described in the XFA spec so tests pass.
- This makes an integer underflow in the implementation go away.

BUG=chromium:956985

Change-Id: I5c488de0b04cca5c93fc75e99d4aa5ad09b4c0fe
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/53618
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
diff --git a/fxjs/xfa/cfxjse_formcalc_context.cpp b/fxjs/xfa/cfxjse_formcalc_context.cpp
index 387879c..b9971c9 100644
--- a/fxjs/xfa/cfxjse_formcalc_context.cpp
+++ b/fxjs/xfa/cfxjse_formcalc_context.cpp
@@ -4272,29 +4272,34 @@
     return;
   }
 
-  std::unique_ptr<CFXJSE_Value> stringValue = GetSimpleValue(pThis, args, 0);
-  std::unique_ptr<CFXJSE_Value> startValue = GetSimpleValue(pThis, args, 1);
-  std::unique_ptr<CFXJSE_Value> endValue = GetSimpleValue(pThis, args, 2);
-  if (ValueIsNull(pThis, stringValue.get()) ||
-      (ValueIsNull(pThis, startValue.get())) ||
-      (ValueIsNull(pThis, endValue.get()))) {
+  std::unique_ptr<CFXJSE_Value> string_value = GetSimpleValue(pThis, args, 0);
+  std::unique_ptr<CFXJSE_Value> start_value = GetSimpleValue(pThis, args, 1);
+  std::unique_ptr<CFXJSE_Value> end_value = GetSimpleValue(pThis, args, 2);
+  if (ValueIsNull(pThis, string_value.get()) ||
+      ValueIsNull(pThis, start_value.get()) ||
+      ValueIsNull(pThis, end_value.get())) {
     args.GetReturnValue()->SetNull();
     return;
   }
 
-  ByteString bsSource = ValueToUTF8String(stringValue.get());
-  int32_t iLength = bsSource.GetLength();
+  ByteString bsSource = ValueToUTF8String(string_value.get());
+  size_t iLength = bsSource.GetLength();
   if (iLength == 0) {
     args.GetReturnValue()->SetString("");
     return;
   }
 
-  int32_t iStart = pdfium::clamp(
-      iLength, 1, static_cast<int32_t>(ValueToFloat(pThis, startValue.get())));
-  int32_t iCount =
-      std::max(0, static_cast<int32_t>(ValueToFloat(pThis, endValue.get())));
+  // |start_value| is 1-based. Assume first character if |start_value| is less
+  // than 1, per spec. Subtract 1 since |iStart| is 0-based.
+  size_t iStart = std::max(ValueToInteger(pThis, start_value.get()), 1) - 1;
+  if (iStart >= iLength) {
+    args.GetReturnValue()->SetString("");
+    return;
+  }
 
-  --iStart;
+  // Negative values are treated as 0. Can't clamp() due to sign mismatches.
+  size_t iCount = std::max(ValueToInteger(pThis, end_value.get()), 0);
+  iCount = std::min(iCount, iLength - iStart);
   args.GetReturnValue()->SetString(bsSource.Mid(iStart, iCount).AsStringView());
 }
 
diff --git a/fxjs/xfa/cfxjse_formcalc_context_embeddertest.cpp b/fxjs/xfa/cfxjse_formcalc_context_embeddertest.cpp
index ff2c47d..77e3880 100644
--- a/fxjs/xfa/cfxjse_formcalc_context_embeddertest.cpp
+++ b/fxjs/xfa/cfxjse_formcalc_context_embeddertest.cpp
@@ -1402,13 +1402,13 @@
   struct {
     const char* program;
     const char* result;
-  } static const kTests[] = {{"Substr(\"ABCDEFG\", -1, 4)", ""},
-                             {"Substr(\"ABCDEFG\", 0, 4)", ""},
+  } static const kTests[] = {{"Substr(\"ABCDEFG\", -1, 4)", "ABCD"},
+                             {"Substr(\"ABCDEFG\", 0, 4)", "ABCD"},
                              {"Substr(\"ABCDEFG\", 3, 4)", "CDEF"},
                              {"Substr(\"ABCDEFG\", 4, 4)", "DEFG"},
-                             {"Substr(\"ABCDEFG\", 5, 4)", ""},
-                             {"Substr(\"ABCDEFG\", 6, 4)", ""},
-                             {"Substr(\"ABCDEFG\", 7, 4)", ""},
+                             {"Substr(\"ABCDEFG\", 5, 4)", "EFG"},
+                             {"Substr(\"ABCDEFG\", 6, 4)", "FG"},
+                             {"Substr(\"ABCDEFG\", 7, 4)", "G"},
                              {"Substr(\"ABCDEFG\", 8, 4)", ""},
                              {"Substr(\"ABCDEFG\", 5, -1)", ""},
                              {"Substr(\"ABCDEFG\", 5, 0)", ""},