From 462291339d55d6ca15322db723dba30cac7e84ea Mon Sep 17 00:00:00 2001
From: Rich Trott <rtrott@gmail.com>
Date: Wed, 10 May 2017 20:59:04 -0700
Subject: [PATCH] timers: do not use user object call/apply

Timers should work even if the user has monkey-patched `.call()` and
`.apply()` to undesirable values.

Refs: https://github.com/nodejs/node/issues/12956
---
 lib/timers.js                          | 12 ++++----
 test/parallel/test-timers-user-call.js | 40 ++++++++++++++++++++++++++
 2 files changed, 46 insertions(+), 6 deletions(-)
 create mode 100644 test/parallel/test-timers-user-call.js

diff --git a/lib/timers.js b/lib/timers.js
index fb9984abf45e05..dd650d3c9acd56 100644
--- a/lib/timers.js
+++ b/lib/timers.js
@@ -485,20 +485,20 @@ function ontimeout(timer) {
   if (typeof callback !== 'function')
     return promiseResolve(callback, args[0]);
   if (!args)
-    callback.call(timer);
+    timer._onTimeout();
   else {
     switch (args.length) {
       case 1:
-        callback.call(timer, args[0]);
+        timer._onTimeout(args[0]);
         break;
       case 2:
-        callback.call(timer, args[0], args[1]);
+        timer._onTimeout(args[0], args[1]);
         break;
       case 3:
-        callback.call(timer, args[0], args[1], args[2]);
+        timer._onTimeout(args[0], args[1], args[2]);
         break;
       default:
-        callback.apply(timer, args);
+        Function.prototype.apply.call(callback, timer, args);
     }
   }
   if (timer._repeat)
@@ -806,7 +806,7 @@ function runCallback(timer) {
       return timer._callback(argv[0], argv[1], argv[2]);
     // more than 3 arguments run slower with .apply
     default:
-      return timer._callback.apply(timer, argv);
+      return Function.prototype.apply.call(timer._callback, timer, argv);
   }
 }
 
diff --git a/test/parallel/test-timers-user-call.js b/test/parallel/test-timers-user-call.js
new file mode 100644
index 00000000000000..4ff24e688b5aa3
--- /dev/null
+++ b/test/parallel/test-timers-user-call.js
@@ -0,0 +1,40 @@
+// Make sure `setTimeout()` and friends don't throw if the user-supplied
+// function has .call() and .apply() monkey-patched to undesirable values.
+
+// Refs: https://github.com/nodejs/node/issues/12956
+
+'use strict';
+
+const common = require('../common');
+
+{
+  const fn = common.mustCall(10);
+  fn.call = 'not a function';
+  fn.apply = 'also not a function';
+  setTimeout(fn, 1);
+  setTimeout(fn, 1, 'oneArg');
+  setTimeout(fn, 1, 'two', 'args');
+  setTimeout(fn, 1, 'three', '(3)', 'args');
+  setTimeout(fn, 1, 'more', 'than', 'three', 'args');
+
+  setImmediate(fn, 1);
+  setImmediate(fn, 1, 'oneArg');
+  setImmediate(fn, 1, 'two', 'args');
+  setImmediate(fn, 1, 'three', '(3)', 'args');
+  setImmediate(fn, 1, 'more', 'than', 'three', 'args');
+}
+
+{
+  const testInterval = (...args) => {
+    const fn = common.mustCall(() => { clearInterval(interval); });
+    fn.call = 'not a function';
+    fn.apply = 'also not a function';
+    const interval = setInterval(fn, 1, ...args);
+  };
+
+  testInterval();
+  testInterval('oneArg');
+  testInterval('two', 'args');
+  testInterval('three', '(3)', 'args');
+  testInterval('more', 'than', 'three', 'args');
+}