Add proxy for syscall time

This CL adds a proxy, FXSYS_time, for the time syscall, so that a
testing mechanism can be implemented. Specically there is now a flag
for pdfium_test, --time=, that allows setting the time since the epoch
that will be returned. This plumbed all the way down into the proxy
and allows for stable results for tests that depend on getting the
current time.

There are other places in the code base that will need to be patched
like this, that will be dealt with in follow on CLs.

BUG=pdfium:1104

Change-Id: I2de185f8d47abe46704dd579c13a54948b7f81e0
Reviewed-on: https://pdfium-review.googlesource.com/39750
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
diff --git a/DEPS b/DEPS
index f211f44..de06b3a 100644
--- a/DEPS
+++ b/DEPS
@@ -29,7 +29,7 @@
   'jinja2_revision': '45571de473282bd1d8b63a8dfcb1fd268d0635d2',
   'jpeg_turbo_revision': '7260e4d8b8e1e40b17f03fafdf1cd83296900f76',
   'markupsafe_revision': '8f45f5cfa0009d2a70589bcda0349b8cb2b72783',
-  'pdfium_tests_revision': '96223aa3d32dcb606f4f33c5647abfd4e413f220',
+  'pdfium_tests_revision': '62a57ad4d90cf1f68f41c24da7ea413750336235',
   'skia_revision': '588f879677d4f36e16a42dd96876534f104c2e2f',
   'tools_memory_revision': 'f7b00daf4df7f6c469f5fbc68d7f40f6bd15d6e6',
   'trace_event_revision': '211b3ed9d0481b4caddbee1322321b86a483ca1f',
diff --git a/core/fxcrt/fx_extension.cpp b/core/fxcrt/fx_extension.cpp
index c754a85..8320b45 100644
--- a/core/fxcrt/fx_extension.cpp
+++ b/core/fxcrt/fx_extension.cpp
@@ -11,6 +11,9 @@
 #include <limits>
 
 #include "third_party/base/compiler_specific.h"
+#include "third_party/base/ptr_util.h"
+
+time_t (*time_func)() = []() -> time_t { return time(nullptr); };
 
 float FXSYS_wcstof(const wchar_t* pwsStr, int32_t iLength, int32_t* pUsedLen) {
   ASSERT(pwsStr);
@@ -167,3 +170,14 @@
   FXSYS_IntToFourHexChars(0xDC00 + unicode % 0x400, buf + 4);
   return 8;
 }
+
+void FXSYS_SetTimeFunction(time_t (*func)()) {
+  time_func = func ? func : []() -> time_t { return time(nullptr); };
+}
+
+time_t FXSYS_time(time_t* tloc) {
+  time_t ret_val = time_func();
+  if (tloc)
+    *tloc = ret_val;
+  return ret_val;
+}
diff --git a/core/fxcrt/fx_extension.h b/core/fxcrt/fx_extension.h
index dcdd64e..6a49ee1 100644
--- a/core/fxcrt/fx_extension.h
+++ b/core/fxcrt/fx_extension.h
@@ -109,4 +109,7 @@
   return lhs < rhs;
 }
 
+void FXSYS_SetTimeFunction(time_t (*func)());
+time_t FXSYS_time(time_t* tloc);
+
 #endif  // CORE_FXCRT_FX_EXTENSION_H_
diff --git a/fpdfsdk/fpdf_ext.cpp b/fpdfsdk/fpdf_ext.cpp
index 6a2ab06..23c3d46 100644
--- a/fpdfsdk/fpdf_ext.cpp
+++ b/fpdfsdk/fpdf_ext.cpp
@@ -10,6 +10,7 @@
 #include "core/fpdfapi/parser/cpdf_document.h"
 #include "core/fpdfdoc/cpdf_interform.h"
 #include "core/fpdfdoc/cpdf_metadata.h"
+#include "core/fxcrt/fx_extension.h"
 #include "fpdfsdk/cpdfsdk_helpers.h"
 
 #ifdef PDF_ENABLE_XFA
@@ -76,6 +77,10 @@
   return true;
 }
 
+FPDF_EXPORT void FPDF_CALLCONV FSDK_SetTimeFunction(time_t (*func)()) {
+  FXSYS_SetTimeFunction(func);
+}
+
 FPDF_EXPORT int FPDF_CALLCONV FPDFDoc_GetPageMode(FPDF_DOCUMENT document) {
   CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
   if (!pDoc)
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c
index 56e9d7a..7dbe164 100644
--- a/fpdfsdk/fpdf_view_c_api_test.c
+++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -217,6 +217,7 @@
 
     // fpdf_ext.h
     CHK(FPDFDoc_GetPageMode);
+    CHK(FSDK_SetTimeFunction);
     CHK(FSDK_SetUnSpObjProcessHandler);
 
     // fpdf_flatten.h
diff --git a/fxjs/cfxjse_formcalc_context.cpp b/fxjs/cfxjse_formcalc_context.cpp
index 9973df6..d164c62 100644
--- a/fxjs/cfxjse_formcalc_context.cpp
+++ b/fxjs/cfxjse_formcalc_context.cpp
@@ -6,8 +6,6 @@
 
 #include "fxjs/cfxjse_formcalc_context.h"
 
-#include <time.h>
-
 #include <algorithm>
 #include <string>
 #include <utility>
@@ -2061,7 +2059,7 @@
   }
 
   time_t currentTime;
-  time(&currentTime);
+  FXSYS_time(&currentTime);
   struct tm* pTmStruct = gmtime(&currentTime);
 
   args.GetReturnValue()->SetInteger(DateString2Num(
diff --git a/public/fpdf_ext.h b/public/fpdf_ext.h
index e488c52..a531f14 100644
--- a/public/fpdf_ext.h
+++ b/public/fpdf_ext.h
@@ -7,6 +7,8 @@
 #ifndef PUBLIC_FPDF_EXT_H_
 #define PUBLIC_FPDF_EXT_H_
 
+#include <time.h>
+
 // NOLINTNEXTLINE(build/include)
 #include "fpdfview.h"
 
@@ -67,6 +69,14 @@
 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
 FSDK_SetUnSpObjProcessHandler(UNSUPPORT_INFO* unsp_info);
 
+// Sets generator function for calls to time.
+//
+// This API is intended to be used only for testing, thus may cause PDFium to
+// behave poorly in production environments.
+//
+//   func - Function pointer to alternate implementation of time.
+FPDF_EXPORT void FPDF_CALLCONV FSDK_SetTimeFunction(time_t (*func)());
+
 // Unknown page mode.
 #define PAGEMODE_UNKNOWN -1
 // Document outline, and thumbnails hidden.
diff --git a/samples/pdfium_test.cc b/samples/pdfium_test.cc
index 14a62a4..b7538a8 100644
--- a/samples/pdfium_test.cc
+++ b/samples/pdfium_test.cc
@@ -115,6 +115,7 @@
   std::string font_directory;
   int first_page = 0;  // First 0-based page number to renderer.
   int last_page = 0;   // Last 0-based page number to renderer.
+  time_t time = -1;
 };
 
 Optional<std::string> ExpandDirectoryPath(const std::string& path) {
@@ -424,6 +425,17 @@
       }
     } else if (cur_arg == "--md5") {
       options->md5 = true;
+    } else if (cur_arg.size() > 7 && cur_arg.compare(0, 7, "--time=") == 0) {
+      if (options->time > -1) {
+        fprintf(stderr, "Duplicate --time argument\n");
+        return false;
+      }
+      const std::string time_string = cur_arg.substr(7);
+      std::stringstream(time_string) >> options->time;
+      if (options->time < 0) {
+        fprintf(stderr, "Invalid --time argument, must be non-negative\n");
+        return false;
+      }
     } else if (cur_arg.size() >= 2 && cur_arg[0] == '-' && cur_arg[1] == '-') {
       fprintf(stderr, "Unrecognized argument %s\n", cur_arg.c_str());
       return false;
@@ -834,6 +846,7 @@
     "  --skp   - write page images <pdf-name>.<page-number>.skp\n"
 #endif
     "  --md5   - write output image paths and their md5 hashes to stdout.\n"
+    "  --time=<number> - Seconds since the epoch to set system time.\n"
     "";
 
 }  // namespace
@@ -889,6 +902,13 @@
 
   FSDK_SetUnSpObjProcessHandler(&unsupported_info);
 
+  if (options.time > -1) {
+    // This must be a static var to avoid explicit capture, so the lambda can be
+    // converted to a function ptr.
+    static time_t time_ret = options.time;
+    FSDK_SetTimeFunction([]() -> time_t { return time_ret; });
+  }
+
   for (const std::string& filename : files) {
     size_t file_length = 0;
     std::unique_ptr<char, pdfium::FreeDeleter> file_contents =
diff --git a/testing/SUPPRESSIONS b/testing/SUPPRESSIONS
index ed2973c..f3d5867 100644
--- a/testing/SUPPRESSIONS
+++ b/testing/SUPPRESSIONS
@@ -299,7 +299,6 @@
 
 Choose.pdf * * *
 data_binding.pdf * * *
-Date_FormCale.pdf * * *
 # TODO(npm): Add proper evt for MouseEvents.
 MouseEvents_enter.pdf * * *
 MouseEvents_exit.pdf * * *
diff --git a/testing/tools/api_check.py b/testing/tools/api_check.py
index 2c5cc4f..934577b 100755
--- a/testing/tools/api_check.py
+++ b/testing/tools/api_check.py
@@ -22,6 +22,8 @@
     return True
   if function == 'FSDK_SetUnSpObjProcessHandler' and filename == 'fpdf_ext.h':
     return True
+  if function == 'FSDK_SetTimeFunction' and filename == 'fpdf_ext.h':
+    return True
   if function.startswith('FORM_') and filename == 'fpdf_formfill.h':
     return True
   return False
diff --git a/testing/tools/make_expected.sh b/testing/tools/make_expected.sh
index a70df79..9b7e3d8 100755
--- a/testing/tools/make_expected.sh
+++ b/testing/tools/make_expected.sh
@@ -6,6 +6,11 @@
 #
 # Script to generate expected result files.
 
+# Arbitrary timestamp, expressed in seconds since the epoch, used to make sure
+# that tests that depend on the current time are stable. Happens to be the
+# timestamp of the first commit to repo, 2014/5/9 17:48:50.
+TEST_SEED_TIME=1399672130
+
 # Do this before "set -e" so "which" failing is not fatal.
 PNGOPTIMIZER="$(which optipng)"
 
@@ -15,9 +20,9 @@
   echo $INFILE | grep -qs ' ' && echo space in filename detected && exit 1
   EVTFILE="${INFILE%.*}.evt"
   if [ -f "$EVTFILE" ]; then
-    out/Debug/pdfium_test --send-events --png $INFILE
+    out/Debug/pdfium_test --send-events --time=$TEST_SEED_TIME --png $INFILE
   else
-    out/Debug/pdfium_test --png $INFILE
+    out/Debug/pdfium_test --time=$TEST_SEED_TIME --png $INFILE
   fi
   RESULTS="$INFILE.*.png"
   for RESULT in $RESULTS ; do
diff --git a/testing/tools/test_runner.py b/testing/tools/test_runner.py
index ecd87ad..43dc578 100644
--- a/testing/tools/test_runner.py
+++ b/testing/tools/test_runner.py
@@ -17,6 +17,11 @@
 import pngdiffer
 import suppressor
 
+# Arbitrary timestamp, expressed in seconds since the epoch, used to make sure
+# that tests that depend on the current time are stable. Happens to be the
+# timestamp of the first commit to repo, 2014/5/9 17:48:50.
+TEST_SEED_TIME = "1399672130"
+
 class KeyboardInterruptError(Exception): pass
 
 # Nomenclature:
@@ -132,14 +137,16 @@
     txt_path = os.path.join(self.working_dir, input_root + '.txt')
 
     with open(txt_path, 'w') as outfile:
-      cmd_to_run = [self.pdfium_test_path, '--send-events', pdf_path]
+      cmd_to_run = [self.pdfium_test_path, '--send-events',
+                    '--time=' + TEST_SEED_TIME, pdf_path]
       subprocess.check_call(cmd_to_run, stdout=outfile)
 
     cmd = [sys.executable, self.text_diff_path, expected_txt_path, txt_path]
     return common.RunCommand(cmd)
 
   def TestPixel(self, input_root, pdf_path, use_ahem):
-    cmd_to_run = [self.pdfium_test_path, '--send-events', '--png', '--md5']
+    cmd_to_run = [self.pdfium_test_path, '--send-events', '--png', '--md5',
+                  '--time=' + TEST_SEED_TIME]
 
     if self.oneshot_renderer:
       cmd_to_run.append('--render-oneshot')