Repository: jprichardson/node-fs-extra (js)

Issue:
[8.0.0] RangeError: Maximum call stack size exceeded at moveSync
<!-- First ensure you installed the latest version of fs-extra -->
<!-- If your bug still exists please fill out the following information if it applies to your issue: -->
- **Operating System:**
Windows [Version 10.0.17763.475]
- **Node.js version:**
v11.14.0
- **`fs-extra` version:**
v8.0.0

In the v7.0.1 is working fine!

```js
// Move
paths.forEach(path => (
  fs.moveSync(`${src}/${path}`, `${dest}${applyConfig(path, config)}`, { overwrite: true })
));
```

`(node:5924) UnhandledPromiseRejectionWarning: RangeError: Maximum call stack size exceeded
    at isPathSeparator (path.js:38:25)
    at Object.resolve (path.js:169:13)
    at Object.toNamespacedPath (path.js:555:32)
    at Object.statSync (fs.js:869:41)
    at checkParentPathsSync (C:\Users\johne\Documents\Elaniin Tech Company\elaniin-cli\node_modules\fs-extra\lib\util\stat.js:143:21)
    at checkParentPathsSync (C:\Users\johne\Documents\Elaniin Tech Company\elaniin-cli\node_modules\fs-extra\lib\util\stat.js:154:10)
    at checkParentPathsSync (C:\Users\johne\Documents\Elaniin Tech Company\elaniin-cli\node_modules\fs-extra\lib\util\stat.js:154:10)
    at checkParentPathsSync (C:\Users\johne\Documents\Elaniin Tech Company\elaniin-cli\node_modules\fs-extra\lib\util\stat.js:154:10)
    at checkParentPathsSync (C:\Users\johne\Documents\Elaniin Tech Company\elaniin-cli\node_modules\fs-extra\lib\util\stat.js:154:10)
    at checkParentPathsSync (C:\Users\johne\Documents\Elaniin Tech Company\elaniin-cli\node_modules\fs-extra\lib\util\stat.js:154:10)
    at checkParentPathsSync (C:\Users\johne\Documents\Elaniin Tech Company\elaniin-cli\node_modules\fs-extra\lib\util\stat.js:154:10)
    at checkParentPathsSync (C:\Users\johne\Documents\Elaniin Tech Company\elaniin-cli\node_modules\fs-extra\lib\util\stat.js:154:10)
    at checkParentPathsSync (C:\Users\johne\Documents\Elaniin Tech Company\elaniin-cli\node_modules\fs-extra\lib\util\stat.js:154:10)
    at checkParentPathsSync (C:\Users\johne\Documents\Elaniin Tech Company\elaniin-cli\node_modules\fs-extra\lib\util\stat.js:154:10)
    at checkParentPathsSync (C:\Users\johne\Documents\Elaniin Tech Company\elaniin-cli\node_modules\fs-extra\lib\util\stat.js:154:10)
    at checkParentPathsSync (C:\Users\johne\Documents\Elaniin Tech Company\elaniin-cli\node_modules\fs-extra\lib\util\stat.js:154:10)`

Pull request:
util/stat: fix max call stack size exceeded
fixes #678.

- resolve paths before parsing in `util/stat.js`
- add `util/stat` tests

Patch:
diff --git a/lib/util/stat.js b/lib/util/stat.js
index 5896a2b..20049b7 100644
--- a/lib/util/stat.js
+++ b/lib/util/stat.js
@@ -104,11 +104,9 @@ function checkPathsSync (src, dest, funcName) {
 // checks the src and dest inodes. It starts from the deepest
 // parent and stops once it reaches the src parent or the root path.
 function checkParentPaths (src, srcStat, dest, funcName, cb) {
-  const destParent = path.dirname(dest)
-  if (destParent &&
-    (destParent === path.dirname(src) ||
-    destParent === path.parse(destParent).root)
-  ) return cb()
+  const srcParent = path.resolve(path.dirname(src))
+  const destParent = path.resolve(path.dirname(dest))
+  if (destParent === srcParent || destParent === path.parse(destParent).root) return cb()
   if (nodeSupportsBigInt()) {
     fs.stat(destParent, { bigint: true }, (err, destStat) => {
       if (err) {
@@ -135,8 +133,9 @@ function checkParentPaths (src, srcStat, dest, funcName, cb) {
 }
 
 function checkParentPathsSync (src, srcStat, dest, funcName) {
-  const destParent = path.dirname(dest)
-  if (destParent && (destParent === path.dirname(src) || destParent === path.parse(destParent).root)) return
+  const srcParent = path.resolve(path.dirname(src))
+  const destParent = path.resolve(path.dirname(dest))
+  if (destParent === srcParent || destParent === path.parse(destParent).root) return
   let destStat
   try {
     if (nodeSupportsBigInt()) {


Test patch:
diff --git a/lib/util/__tests__/stat.test.js b/lib/util/__tests__/stat.test.js
new file mode 100644
index 0000000..223832f
--- /dev/null
+++ b/lib/util/__tests__/stat.test.js
@@ -0,0 +1,80 @@
+'use strict'
+
+const fs = require(process.cwd())
+const os = require('os')
+const path = require('path')
+const assert = require('assert')
+const semver = require('semver')
+const stat = require('../stat.js')
+
+const NODE_VERSION_WITH_BIGINT = '10.5.0'
+
+/* global beforeEach, afterEach, describe, it */
+
+describe('util/stat', () => {
+  let TEST_DIR
+
+  beforeEach(done => {
+    TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'util-stat')
+    fs.emptyDir(TEST_DIR, done)
+  })
+
+  afterEach(done => fs.remove(TEST_DIR, done))
+
+  describe('should use stats with bigint type for node versions >= 10.5.0 and number type for older versions', done => {
+    it('stat.checkPaths()', () => {
+      const nodeVersion = process.versions.node
+      const src = path.join(TEST_DIR, 'src')
+      const dest = path.join(TEST_DIR, 'dest')
+      fs.ensureFileSync(src)
+      fs.ensureFileSync(dest)
+      stat.checkPaths(src, dest, 'copy', (err, stats) => {
+        assert.ifError(err)
+        const { srcStat } = stats
+        if (semver.gte(nodeVersion, NODE_VERSION_WITH_BIGINT)) {
+          assert.strictEqual(typeof srcStat.ino, 'bigint')
+        } else {
+          assert.strictEqual(typeof srcStat.ino, 'number')
+        }
+      })
+    })
+
+    it('stat.checkPathsSync()', () => {
+      const nodeVersion = process.versions.node
+      const src = path.join(TEST_DIR, 'src')
+      const dest = path.join(TEST_DIR, 'dest')
+      fs.ensureFileSync(src)
+      fs.ensureFileSync(dest)
+      const { srcStat } = stat.checkPathsSync(src, dest, 'copy')
+      if (semver.gte(nodeVersion, NODE_VERSION_WITH_BIGINT)) {
+        assert.strictEqual(typeof srcStat.ino, 'bigint')
+      } else {
+        assert.strictEqual(typeof srcStat.ino, 'number')
+      }
+    })
+  })
+
+  describe('should stop at src or root path and not throw max call stack size error', done => {
+    it('stat.checkParentPaths()', () => {
+      const src = path.join(TEST_DIR, 'src')
+      let dest = path.join(TEST_DIR, 'dest')
+      fs.ensureFileSync(src)
+      fs.ensureFileSync(dest)
+      dest = path.basename(dest)
+      const srcStat = fs.statSync(src)
+      stat.checkParentPaths(src, srcStat, dest, 'copy', err => {
+        assert.ifError(err)
+      })
+    })
+
+    it('stat.checkParentPathsSync()', () => {
+      const src = path.join(TEST_DIR, 'src')
+      let dest = path.join(TEST_DIR, 'dest')
+      fs.ensureFileSync(src)
+      fs.ensureFileSync(dest)
+      dest = path.basename(dest)
+      const srcStat = fs.statSync(src)
+      stat.checkParentPathsSync(src, srcStat, dest, 'copy')
+    })
+  })
+})