Projekt

Obecné

Profil

Stáhnout (10.2 KB) Statistiky
| Větev: | Revize:
1
'use strict';
2

    
3
const Module = require('module');
4
const crypto = require('crypto');
5
const fs = require('fs');
6
const path = require('path');
7
const vm = require('vm');
8
const os = require('os');
9

    
10
const hasOwnProperty = Object.prototype.hasOwnProperty;
11

    
12
//------------------------------------------------------------------------------
13
// FileSystemBlobStore
14
//------------------------------------------------------------------------------
15

    
16
class FileSystemBlobStore {
17
  constructor(directory, prefix) {
18
    const name = prefix ? slashEscape(prefix + '.') : '';
19
    this._blobFilename = path.join(directory, name + 'BLOB');
20
    this._mapFilename = path.join(directory, name + 'MAP');
21
    this._lockFilename = path.join(directory, name + 'LOCK');
22
    this._directory = directory;
23
    this._load();
24
  }
25

    
26
  has(key, invalidationKey) {
27
    if (hasOwnProperty.call(this._memoryBlobs, key)) {
28
      return this._invalidationKeys[key] === invalidationKey;
29
    } else if (hasOwnProperty.call(this._storedMap, key)) {
30
      return this._storedMap[key][0] === invalidationKey;
31
    }
32
    return false;
33
  }
34

    
35
  get(key, invalidationKey) {
36
    if (hasOwnProperty.call(this._memoryBlobs, key)) {
37
      if (this._invalidationKeys[key] === invalidationKey) {
38
        return this._memoryBlobs[key];
39
      }
40
    } else if (hasOwnProperty.call(this._storedMap, key)) {
41
      const mapping = this._storedMap[key];
42
      if (mapping[0] === invalidationKey) {
43
        return this._storedBlob.slice(mapping[1], mapping[2]);
44
      }
45
    }
46
  }
47

    
48
  set(key, invalidationKey, buffer) {
49
    this._invalidationKeys[key] = invalidationKey;
50
    this._memoryBlobs[key] = buffer;
51
    this._dirty = true;
52
  }
53

    
54
  delete(key) {
55
    if (hasOwnProperty.call(this._memoryBlobs, key)) {
56
      this._dirty = true;
57
      delete this._memoryBlobs[key];
58
    }
59
    if (hasOwnProperty.call(this._invalidationKeys, key)) {
60
      this._dirty = true;
61
      delete this._invalidationKeys[key];
62
    }
63
    if (hasOwnProperty.call(this._storedMap, key)) {
64
      this._dirty = true;
65
      delete this._storedMap[key];
66
    }
67
  }
68

    
69
  isDirty() {
70
    return this._dirty;
71
  }
72

    
73
  save() {
74
    const dump = this._getDump();
75
    const blobToStore = Buffer.concat(dump[0]);
76
    const mapToStore = JSON.stringify(dump[1]);
77

    
78
    try {
79
      mkdirpSync(this._directory);
80
      fs.writeFileSync(this._lockFilename, 'LOCK', {flag: 'wx'});
81
    } catch (error) {
82
      // Swallow the exception if we fail to acquire the lock.
83
      return false;
84
    }
85

    
86
    try {
87
      fs.writeFileSync(this._blobFilename, blobToStore);
88
      fs.writeFileSync(this._mapFilename, mapToStore);
89
    } catch (error) {
90
      throw error;
91
    } finally {
92
      fs.unlinkSync(this._lockFilename);
93
    }
94

    
95
    return true;
96
  }
97

    
98
  _load() {
99
    try {
100
      this._storedBlob = fs.readFileSync(this._blobFilename);
101
      this._storedMap = JSON.parse(fs.readFileSync(this._mapFilename));
102
    } catch (e) {
103
      this._storedBlob = Buffer.alloc(0);
104
      this._storedMap = {};
105
    }
106
    this._dirty = false;
107
    this._memoryBlobs = {};
108
    this._invalidationKeys = {};
109
  }
110

    
111
  _getDump() {
112
    const buffers = [];
113
    const newMap = {};
114
    let offset = 0;
115

    
116
    function push(key, invalidationKey, buffer) {
117
      buffers.push(buffer);
118
      newMap[key] = [invalidationKey, offset, offset + buffer.length];
119
      offset += buffer.length;
120
    }
121

    
122
    for (const key of Object.keys(this._memoryBlobs)) {
123
      const buffer = this._memoryBlobs[key];
124
      const invalidationKey = this._invalidationKeys[key];
125
      push(key, invalidationKey, buffer);
126
    }
127

    
128
    for (const key of Object.keys(this._storedMap)) {
129
      if (hasOwnProperty.call(newMap, key)) continue;
130
      const mapping = this._storedMap[key];
131
      const buffer = this._storedBlob.slice(mapping[1], mapping[2]);
132
      push(key, mapping[0], buffer);
133
    }
134

    
135
    return [buffers, newMap];
136
  }
137
}
138

    
139
//------------------------------------------------------------------------------
140
// NativeCompileCache
141
//------------------------------------------------------------------------------
142

    
143
class NativeCompileCache {
144
  constructor() {
145
    this._cacheStore = null;
146
    this._previousModuleCompile = null;
147
  }
148

    
149
  setCacheStore(cacheStore) {
150
    this._cacheStore = cacheStore;
151
  }
152

    
153
  install() {
154
    const self = this;
155
    const hasRequireResolvePaths = typeof require.resolve.paths === 'function';
156
    this._previousModuleCompile = Module.prototype._compile;
157
    Module.prototype._compile = function(content, filename) {
158
      const mod = this;
159

    
160
      function require(id) {
161
        return mod.require(id);
162
      }
163

    
164
      // https://github.com/nodejs/node/blob/v10.15.3/lib/internal/modules/cjs/helpers.js#L28
165
      function resolve(request, options) {
166
        return Module._resolveFilename(request, mod, false, options);
167
      }
168
      require.resolve = resolve;
169

    
170
      // https://github.com/nodejs/node/blob/v10.15.3/lib/internal/modules/cjs/helpers.js#L37
171
      // resolve.resolve.paths was added in v8.9.0
172
      if (hasRequireResolvePaths) {
173
        resolve.paths = function paths(request) {
174
          return Module._resolveLookupPaths(request, mod, true);
175
        };
176
      }
177

    
178
      require.main = process.mainModule;
179

    
180
      // Enable support to add extra extension types
181
      require.extensions = Module._extensions;
182
      require.cache = Module._cache;
183

    
184
      const dirname = path.dirname(filename);
185

    
186
      const compiledWrapper = self._moduleCompile(filename, content);
187

    
188
      // We skip the debugger setup because by the time we run, node has already
189
      // done that itself.
190

    
191
      const args = [mod.exports, require, mod, filename, dirname, process, global];
192
      return compiledWrapper.apply(mod.exports, args);
193
    };
194
  }
195

    
196
  uninstall() {
197
    Module.prototype._compile = this._previousModuleCompile;
198
  }
199

    
200
  _moduleCompile(filename, content) {
201
    // https://github.com/nodejs/node/blob/v7.5.0/lib/module.js#L511
202

    
203
    // Remove shebang
204
    var contLen = content.length;
205
    if (contLen >= 2) {
206
      if (content.charCodeAt(0) === 35/*#*/ &&
207
          content.charCodeAt(1) === 33/*!*/) {
208
        if (contLen === 2) {
209
          // Exact match
210
          content = '';
211
        } else {
212
          // Find end of shebang line and slice it off
213
          var i = 2;
214
          for (; i < contLen; ++i) {
215
            var code = content.charCodeAt(i);
216
            if (code === 10/*\n*/ || code === 13/*\r*/) break;
217
          }
218
          if (i === contLen) {
219
            content = '';
220
          } else {
221
            // Note that this actually includes the newline character(s) in the
222
            // new output. This duplicates the behavior of the regular
223
            // expression that was previously used to replace the shebang line
224
            content = content.slice(i);
225
          }
226
        }
227
      }
228
    }
229

    
230
    // create wrapper function
231
    var wrapper = Module.wrap(content);
232

    
233
    var invalidationKey = crypto
234
      .createHash('sha1')
235
      .update(content, 'utf8')
236
      .digest('hex');
237

    
238
    var buffer = this._cacheStore.get(filename, invalidationKey);
239

    
240
    var script = new vm.Script(wrapper, {
241
      filename: filename,
242
      lineOffset: 0,
243
      displayErrors: true,
244
      cachedData: buffer,
245
      produceCachedData: true,
246
    });
247

    
248
    if (script.cachedDataProduced) {
249
      this._cacheStore.set(filename, invalidationKey, script.cachedData);
250
    } else if (script.cachedDataRejected) {
251
      this._cacheStore.delete(filename);
252
    }
253

    
254
    var compiledWrapper = script.runInThisContext({
255
      filename: filename,
256
      lineOffset: 0,
257
      columnOffset: 0,
258
      displayErrors: true,
259
    });
260

    
261
    return compiledWrapper;
262
  }
263
}
264

    
265
//------------------------------------------------------------------------------
266
// utilities
267
//
268
// https://github.com/substack/node-mkdirp/blob/f2003bb/index.js#L55-L98
269
// https://github.com/zertosh/slash-escape/blob/e7ebb99/slash-escape.js
270
//------------------------------------------------------------------------------
271

    
272
function mkdirpSync(p_) {
273
  _mkdirpSync(path.resolve(p_), parseInt('0777', 8) & ~process.umask());
274
}
275

    
276
function _mkdirpSync(p, mode) {
277
  try {
278
    fs.mkdirSync(p, mode);
279
  } catch (err0) {
280
    if (err0.code === 'ENOENT') {
281
      _mkdirpSync(path.dirname(p));
282
      _mkdirpSync(p);
283
    } else {
284
      try {
285
        const stat = fs.statSync(p);
286
        if (!stat.isDirectory()) { throw err0; }
287
      } catch (err1) {
288
        throw err0;
289
      }
290
    }
291
  }
292
}
293

    
294
function slashEscape(str) {
295
  const ESCAPE_LOOKUP = {
296
    '\\': 'zB',
297
    ':': 'zC',
298
    '/': 'zS',
299
    '\x00': 'z0',
300
    'z': 'zZ',
301
  };
302
  return str.replace(/[\\:\/\x00z]/g, match => (ESCAPE_LOOKUP[match]));
303
}
304

    
305
function supportsCachedData() {
306
  const script = new vm.Script('""', {produceCachedData: true});
307
  // chakracore, as of v1.7.1.0, returns `false`.
308
  return script.cachedDataProduced === true;
309
}
310

    
311
function getCacheDir() {
312
  // Avoid cache ownership issues on POSIX systems.
313
  const dirname = typeof process.getuid === 'function'
314
    ? 'v8-compile-cache-' + process.getuid()
315
    : 'v8-compile-cache';
316
  const version = typeof process.versions.v8 === 'string'
317
    ? process.versions.v8
318
    : typeof process.versions.chakracore === 'string'
319
      ? 'chakracore-' + process.versions.chakracore
320
      : 'node-' + process.version;
321
  const cacheDir = path.join(os.tmpdir(), dirname, version);
322
  return cacheDir;
323
}
324

    
325
function getParentName() {
326
  // `module.parent.filename` is undefined or null when:
327
  //    * node -e 'require("v8-compile-cache")'
328
  //    * node -r 'v8-compile-cache'
329
  //    * Or, requiring from the REPL.
330
  const parentName = module.parent && typeof module.parent.filename === 'string'
331
    ? module.parent.filename
332
    : process.cwd();
333
  return parentName;
334
}
335

    
336
//------------------------------------------------------------------------------
337
// main
338
//------------------------------------------------------------------------------
339

    
340
if (!process.env.DISABLE_V8_COMPILE_CACHE && supportsCachedData()) {
341
  const cacheDir = getCacheDir();
342
  const prefix = getParentName();
343
  const blobStore = new FileSystemBlobStore(cacheDir, prefix);
344

    
345
  const nativeCompileCache = new NativeCompileCache();
346
  nativeCompileCache.setCacheStore(blobStore);
347
  nativeCompileCache.install();
348

    
349
  process.once('exit', code => {
350
    if (blobStore.isDirty()) {
351
      blobStore.save();
352
    }
353
    nativeCompileCache.uninstall();
354
  });
355
}
356

    
357
module.exports.__TEST__ = {
358
  FileSystemBlobStore,
359
  NativeCompileCache,
360
  mkdirpSync,
361
  slashEscape,
362
  supportsCachedData,
363
  getCacheDir,
364
  getParentName,
365
};
(5-5/5)