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 |
|
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 |
|
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 |
|
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 | this._previousModuleCompile = Module.prototype._compile;
|
156 | Module.prototype._compile = function(content, filename) {
|
157 | const mod = this;
|
158 | function require(id) {
|
159 | return mod.require(id);
|
160 | }
|
161 | require.resolve = function(request) {
|
162 | return Module._resolveFilename(request, mod);
|
163 | };
|
164 | require.main = process.mainModule;
|
165 |
|
166 |
|
167 | require.extensions = Module._extensions;
|
168 | require.cache = Module._cache;
|
169 |
|
170 | const dirname = path.dirname(filename);
|
171 |
|
172 | const compiledWrapper = self._moduleCompile(filename, content);
|
173 |
|
174 |
|
175 |
|
176 |
|
177 | const args = [mod.exports, require, mod, filename, dirname, process, global];
|
178 | return compiledWrapper.apply(mod.exports, args);
|
179 | };
|
180 | }
|
181 |
|
182 | uninstall() {
|
183 | Module.prototype._compile = this._previousModuleCompile;
|
184 | }
|
185 |
|
186 | _moduleCompile(filename, content) {
|
187 |
|
188 |
|
189 |
|
190 | var contLen = content.length;
|
191 | if (contLen >= 2) {
|
192 | if (content.charCodeAt(0) === 35 &&
|
193 | content.charCodeAt(1) === 33) {
|
194 | if (contLen === 2) {
|
195 |
|
196 | content = '';
|
197 | } else {
|
198 |
|
199 | var i = 2;
|
200 | for (; i < contLen; ++i) {
|
201 | var code = content.charCodeAt(i);
|
202 | if (code === 10 || code === 13) break;
|
203 | }
|
204 | if (i === contLen) {
|
205 | content = '';
|
206 | } else {
|
207 |
|
208 |
|
209 |
|
210 | content = content.slice(i);
|
211 | }
|
212 | }
|
213 | }
|
214 | }
|
215 |
|
216 |
|
217 | var wrapper = Module.wrap(content);
|
218 |
|
219 | var invalidationKey = crypto
|
220 | .createHash('sha1')
|
221 | .update(content, 'utf8')
|
222 | .digest('hex');
|
223 |
|
224 | var buffer = this._cacheStore.get(filename, invalidationKey);
|
225 |
|
226 | var script = new vm.Script(wrapper, {
|
227 | filename: filename,
|
228 | lineOffset: 0,
|
229 | displayErrors: true,
|
230 | cachedData: buffer,
|
231 | produceCachedData: true,
|
232 | });
|
233 |
|
234 | if (script.cachedDataProduced) {
|
235 | this._cacheStore.set(filename, invalidationKey, script.cachedData);
|
236 | } else if (script.cachedDataRejected) {
|
237 | this._cacheStore.delete(filename);
|
238 | }
|
239 |
|
240 | var compiledWrapper = script.runInThisContext({
|
241 | filename: filename,
|
242 | lineOffset: 0,
|
243 | columnOffset: 0,
|
244 | displayErrors: true,
|
245 | });
|
246 |
|
247 | return compiledWrapper;
|
248 | }
|
249 | }
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 | function mkdirpSync(p_) {
|
259 | _mkdirpSync(path.resolve(p_), parseInt('0777', 8) & ~process.umask());
|
260 | }
|
261 |
|
262 | function _mkdirpSync(p, mode) {
|
263 | try {
|
264 | fs.mkdirSync(p, mode);
|
265 | } catch (err0) {
|
266 | if (err0.code === 'ENOENT') {
|
267 | _mkdirpSync(path.dirname(p));
|
268 | _mkdirpSync(p);
|
269 | } else {
|
270 | try {
|
271 | const stat = fs.statSync(p);
|
272 | if (!stat.isDirectory()) { throw err0; }
|
273 | } catch (err1) {
|
274 | throw err0;
|
275 | }
|
276 | }
|
277 | }
|
278 | }
|
279 |
|
280 | function slashEscape(str) {
|
281 | const ESCAPE_LOOKUP = {
|
282 | '\\': 'zB',
|
283 | ':': 'zC',
|
284 | '/': 'zS',
|
285 | '\x00': 'z0',
|
286 | 'z': 'zZ',
|
287 | };
|
288 | return str.replace(/[\\:\/\x00z]/g, match => (ESCAPE_LOOKUP[match]));
|
289 | }
|
290 |
|
291 | function supportsCachedData() {
|
292 | const script = new vm.Script('""', {produceCachedData: true});
|
293 |
|
294 | return script.cachedDataProduced === true;
|
295 | }
|
296 |
|
297 | function getCacheDir() {
|
298 |
|
299 | const dirname = typeof process.getuid === 'function'
|
300 | ? 'v8-compile-cache-' + process.getuid()
|
301 | : 'v8-compile-cache';
|
302 | const version = typeof process.versions.v8 === 'string'
|
303 | ? process.versions.v8
|
304 | : typeof process.versions.chakracore === 'string'
|
305 | ? 'chakracore-' + process.versions.chakracore
|
306 | : 'node-' + process.version;
|
307 | const cacheDir = path.join(os.tmpdir(), dirname, version);
|
308 | return cacheDir;
|
309 | }
|
310 |
|
311 | function getParentName() {
|
312 |
|
313 |
|
314 |
|
315 |
|
316 | const parentName = module.parent && typeof module.parent.filename === 'string'
|
317 | ? module.parent.filename
|
318 | : process.cwd();
|
319 | return parentName;
|
320 | }
|
321 |
|
322 |
|
323 |
|
324 |
|
325 |
|
326 | if (!process.env.DISABLE_V8_COMPILE_CACHE && supportsCachedData()) {
|
327 | const cacheDir = getCacheDir();
|
328 | const prefix = getParentName();
|
329 | const blobStore = new FileSystemBlobStore(cacheDir, prefix);
|
330 |
|
331 | const nativeCompileCache = new NativeCompileCache();
|
332 | nativeCompileCache.setCacheStore(blobStore);
|
333 | nativeCompileCache.install();
|
334 |
|
335 | process.once('exit', code => {
|
336 | if (blobStore.isDirty()) {
|
337 | blobStore.save();
|
338 | }
|
339 | nativeCompileCache.uninstall();
|
340 | });
|
341 | }
|
342 |
|
343 | module.exports.__TEST__ = {
|
344 | FileSystemBlobStore,
|
345 | NativeCompileCache,
|
346 | mkdirpSync,
|
347 | slashEscape,
|
348 | supportsCachedData,
|
349 | getCacheDir,
|
350 | getParentName,
|
351 | };
|