Projekt

Obecné

Profil

Stáhnout (21.2 KB) Statistiky
| Větev: | Revize:
1 3a515b92 cagy
/*
2
  Copyright (C) 2015 Yusuke Suzuki <utatane.tea@gmail.com>
3
4
  Redistribution and use in source and binary forms, with or without
5
  modification, are permitted provided that the following conditions are met:
6
7
    * Redistributions of source code must retain the above copyright
8
      notice, this list of conditions and the following disclaimer.
9
    * Redistributions in binary form must reproduce the above copyright
10
      notice, this list of conditions and the following disclaimer in the
11
      documentation and/or other materials provided with the distribution.
12
13
  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16
  ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
17
  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22
  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
*/
24
"use strict";
25
26
/* eslint-disable no-underscore-dangle */
27
/* eslint-disable no-undefined */
28
29
const Syntax = require("estraverse").Syntax;
30
31
const Reference = require("./reference");
32
const Variable = require("./variable");
33
const Definition = require("./definition").Definition;
34
const assert = require("assert");
35
36
/**
37
 * Test if scope is struct
38
 * @param {Scope} scope - scope
39
 * @param {Block} block - block
40
 * @param {boolean} isMethodDefinition - is method definiton
41
 * @param {boolean} useDirective - use directive
42
 * @returns {boolean} is strict scope
43
 */
44
function isStrictScope(scope, block, isMethodDefinition, useDirective) {
45
    let body;
46
47
    // When upper scope is exists and strict, inner scope is also strict.
48
    if (scope.upper && scope.upper.isStrict) {
49
        return true;
50
    }
51
52
    if (isMethodDefinition) {
53
        return true;
54
    }
55
56
    if (scope.type === "class" || scope.type === "module") {
57
        return true;
58
    }
59
60
    if (scope.type === "block" || scope.type === "switch") {
61
        return false;
62
    }
63
64
    if (scope.type === "function") {
65
        if (block.type === Syntax.ArrowFunctionExpression && block.body.type !== Syntax.BlockStatement) {
66
            return false;
67
        }
68
69
        if (block.type === Syntax.Program) {
70
            body = block;
71
        } else {
72
            body = block.body;
73
        }
74
75
        if (!body) {
76
            return false;
77
        }
78
    } else if (scope.type === "global") {
79
        body = block;
80
    } else {
81
        return false;
82
    }
83
84
    // Search 'use strict' directive.
85
    if (useDirective) {
86
        for (let i = 0, iz = body.body.length; i < iz; ++i) {
87
            const stmt = body.body[i];
88
89
            if (stmt.type !== Syntax.DirectiveStatement) {
90
                break;
91
            }
92
            if (stmt.raw === "\"use strict\"" || stmt.raw === "'use strict'") {
93
                return true;
94
            }
95
        }
96
    } else {
97
        for (let i = 0, iz = body.body.length; i < iz; ++i) {
98
            const stmt = body.body[i];
99
100
            if (stmt.type !== Syntax.ExpressionStatement) {
101
                break;
102
            }
103
            const expr = stmt.expression;
104
105
            if (expr.type !== Syntax.Literal || typeof expr.value !== "string") {
106
                break;
107
            }
108
            if (expr.raw !== null && expr.raw !== undefined) {
109
                if (expr.raw === "\"use strict\"" || expr.raw === "'use strict'") {
110
                    return true;
111
                }
112
            } else {
113
                if (expr.value === "use strict") {
114
                    return true;
115
                }
116
            }
117
        }
118
    }
119
    return false;
120
}
121
122
/**
123
 * Register scope
124
 * @param {ScopeManager} scopeManager - scope manager
125
 * @param {Scope} scope - scope
126
 * @returns {void}
127
 */
128
function registerScope(scopeManager, scope) {
129
    scopeManager.scopes.push(scope);
130
131
    const scopes = scopeManager.__nodeToScope.get(scope.block);
132
133
    if (scopes) {
134
        scopes.push(scope);
135
    } else {
136
        scopeManager.__nodeToScope.set(scope.block, [scope]);
137
    }
138
}
139
140
/**
141
 * Should be statically
142
 * @param {Object} def - def
143
 * @returns {boolean} should be statically
144
 */
145
function shouldBeStatically(def) {
146
    return (
147
        (def.type === Variable.ClassName) ||
148
        (def.type === Variable.Variable && def.parent.kind !== "var")
149
    );
150
}
151
152
/**
153
 * @class Scope
154
 */
155
class Scope {
156
    constructor(scopeManager, type, upperScope, block, isMethodDefinition) {
157
158
        /**
159
         * One of 'module', 'block', 'switch', 'function', 'catch', 'with', 'function', 'class', 'global'.
160
         * @member {String} Scope#type
161
         */
162
        this.type = type;
163
164
         /**
165
         * The scoped {@link Variable}s of this scope, as <code>{ Variable.name
166
         * : Variable }</code>.
167
         * @member {Map} Scope#set
168
         */
169
        this.set = new Map();
170
171
        /**
172
         * The tainted variables of this scope, as <code>{ Variable.name :
173
         * boolean }</code>.
174
         * @member {Map} Scope#taints */
175
        this.taints = new Map();
176
177
        /**
178
         * Generally, through the lexical scoping of JS you can always know
179
         * which variable an identifier in the source code refers to. There are
180
         * a few exceptions to this rule. With 'global' and 'with' scopes you
181
         * can only decide at runtime which variable a reference refers to.
182
         * Moreover, if 'eval()' is used in a scope, it might introduce new
183
         * bindings in this or its parent scopes.
184
         * All those scopes are considered 'dynamic'.
185
         * @member {boolean} Scope#dynamic
186
         */
187
        this.dynamic = this.type === "global" || this.type === "with";
188
189
        /**
190
         * A reference to the scope-defining syntax node.
191
         * @member {espree.Node} Scope#block
192
         */
193
        this.block = block;
194
195
         /**
196
         * The {@link Reference|references} that are not resolved with this scope.
197
         * @member {Reference[]} Scope#through
198
         */
199
        this.through = [];
200
201
         /**
202
         * The scoped {@link Variable}s of this scope. In the case of a
203
         * 'function' scope this includes the automatic argument <em>arguments</em> as
204
         * its first element, as well as all further formal arguments.
205
         * @member {Variable[]} Scope#variables
206
         */
207
        this.variables = [];
208
209
         /**
210
         * Any variable {@link Reference|reference} found in this scope. This
211
         * includes occurrences of local variables as well as variables from
212
         * parent scopes (including the global scope). For local variables
213
         * this also includes defining occurrences (like in a 'var' statement).
214
         * In a 'function' scope this does not include the occurrences of the
215
         * formal parameter in the parameter list.
216
         * @member {Reference[]} Scope#references
217
         */
218
        this.references = [];
219
220
         /**
221
         * For 'global' and 'function' scopes, this is a self-reference. For
222
         * other scope types this is the <em>variableScope</em> value of the
223
         * parent scope.
224
         * @member {Scope} Scope#variableScope
225
         */
226
        this.variableScope =
227
            (this.type === "global" || this.type === "function" || this.type === "module") ? this : upperScope.variableScope;
228
229
         /**
230
         * Whether this scope is created by a FunctionExpression.
231
         * @member {boolean} Scope#functionExpressionScope
232
         */
233
        this.functionExpressionScope = false;
234
235
         /**
236
         * Whether this is a scope that contains an 'eval()' invocation.
237
         * @member {boolean} Scope#directCallToEvalScope
238
         */
239
        this.directCallToEvalScope = false;
240
241
         /**
242
         * @member {boolean} Scope#thisFound
243
         */
244
        this.thisFound = false;
245
246
        this.__left = [];
247
248
         /**
249
         * Reference to the parent {@link Scope|scope}.
250
         * @member {Scope} Scope#upper
251
         */
252
        this.upper = upperScope;
253
254
         /**
255
         * Whether 'use strict' is in effect in this scope.
256
         * @member {boolean} Scope#isStrict
257
         */
258
        this.isStrict = isStrictScope(this, block, isMethodDefinition, scopeManager.__useDirective());
259
260
         /**
261
         * List of nested {@link Scope}s.
262
         * @member {Scope[]} Scope#childScopes
263
         */
264
        this.childScopes = [];
265
        if (this.upper) {
266
            this.upper.childScopes.push(this);
267
        }
268
269
        this.__declaredVariables = scopeManager.__declaredVariables;
270
271
        registerScope(scopeManager, this);
272
    }
273
274
    __shouldStaticallyClose(scopeManager) {
275
        return (!this.dynamic || scopeManager.__isOptimistic());
276
    }
277
278
    __shouldStaticallyCloseForGlobal(ref) {
279
280
        // On global scope, let/const/class declarations should be resolved statically.
281
        const name = ref.identifier.name;
282
283
        if (!this.set.has(name)) {
284
            return false;
285
        }
286
287
        const variable = this.set.get(name);
288
        const defs = variable.defs;
289
290
        return defs.length > 0 && defs.every(shouldBeStatically);
291
    }
292
293
    __staticCloseRef(ref) {
294
        if (!this.__resolve(ref)) {
295
            this.__delegateToUpperScope(ref);
296
        }
297
    }
298
299
    __dynamicCloseRef(ref) {
300
301
        // notify all names are through to global
302
        let current = this;
303
304
        do {
305
            current.through.push(ref);
306
            current = current.upper;
307
        } while (current);
308
    }
309
310
    __globalCloseRef(ref) {
311
312
        // let/const/class declarations should be resolved statically.
313
        // others should be resolved dynamically.
314
        if (this.__shouldStaticallyCloseForGlobal(ref)) {
315
            this.__staticCloseRef(ref);
316
        } else {
317
            this.__dynamicCloseRef(ref);
318
        }
319
    }
320
321
    __close(scopeManager) {
322
        let closeRef;
323
324
        if (this.__shouldStaticallyClose(scopeManager)) {
325
            closeRef = this.__staticCloseRef;
326
        } else if (this.type !== "global") {
327
            closeRef = this.__dynamicCloseRef;
328
        } else {
329
            closeRef = this.__globalCloseRef;
330
        }
331
332
        // Try Resolving all references in this scope.
333
        for (let i = 0, iz = this.__left.length; i < iz; ++i) {
334
            const ref = this.__left[i];
335
336
            closeRef.call(this, ref);
337
        }
338
        this.__left = null;
339
340
        return this.upper;
341
    }
342
343
    // To override by function scopes.
344
    // References in default parameters isn't resolved to variables which are in their function body.
345
    __isValidResolution(ref, variable) { // eslint-disable-line class-methods-use-this, no-unused-vars
346
        return true;
347
    }
348
349
    __resolve(ref) {
350
        const name = ref.identifier.name;
351
352
        if (!this.set.has(name)) {
353
            return false;
354
        }
355
        const variable = this.set.get(name);
356
357
        if (!this.__isValidResolution(ref, variable)) {
358
            return false;
359
        }
360
        variable.references.push(ref);
361
        variable.stack = variable.stack && ref.from.variableScope === this.variableScope;
362
        if (ref.tainted) {
363
            variable.tainted = true;
364
            this.taints.set(variable.name, true);
365
        }
366
        ref.resolved = variable;
367
368
        return true;
369
    }
370
371
    __delegateToUpperScope(ref) {
372
        if (this.upper) {
373
            this.upper.__left.push(ref);
374
        }
375
        this.through.push(ref);
376
    }
377
378
    __addDeclaredVariablesOfNode(variable, node) {
379
        if (node === null || node === undefined) {
380
            return;
381
        }
382
383
        let variables = this.__declaredVariables.get(node);
384
385
        if (variables === null || variables === undefined) {
386
            variables = [];
387
            this.__declaredVariables.set(node, variables);
388
        }
389
        if (variables.indexOf(variable) === -1) {
390
            variables.push(variable);
391
        }
392
    }
393
394
    __defineGeneric(name, set, variables, node, def) {
395
        let variable;
396
397
        variable = set.get(name);
398
        if (!variable) {
399
            variable = new Variable(name, this);
400
            set.set(name, variable);
401
            variables.push(variable);
402
        }
403
404
        if (def) {
405
            variable.defs.push(def);
406
            this.__addDeclaredVariablesOfNode(variable, def.node);
407
            this.__addDeclaredVariablesOfNode(variable, def.parent);
408
        }
409
        if (node) {
410
            variable.identifiers.push(node);
411
        }
412
    }
413
414
    __define(node, def) {
415
        if (node && node.type === Syntax.Identifier) {
416
            this.__defineGeneric(
417
                    node.name,
418
                    this.set,
419
                    this.variables,
420
                    node,
421
                    def);
422
        }
423
    }
424
425
    __referencing(node, assign, writeExpr, maybeImplicitGlobal, partial, init) {
426
427
        // because Array element may be null
428
        if (!node || node.type !== Syntax.Identifier) {
429
            return;
430
        }
431
432
        // Specially handle like `this`.
433
        if (node.name === "super") {
434
            return;
435
        }
436
437
        const ref = new Reference(node, this, assign || Reference.READ, writeExpr, maybeImplicitGlobal, !!partial, !!init);
438
439
        this.references.push(ref);
440
        this.__left.push(ref);
441
    }
442
443
    __detectEval() {
444
        let current = this;
445
446
        this.directCallToEvalScope = true;
447
        do {
448
            current.dynamic = true;
449
            current = current.upper;
450
        } while (current);
451
    }
452
453
    __detectThis() {
454
        this.thisFound = true;
455
    }
456
457
    __isClosed() {
458
        return this.__left === null;
459
    }
460
461
    /**
462
     * returns resolved {Reference}
463
     * @method Scope#resolve
464
     * @param {Espree.Identifier} ident - identifier to be resolved.
465
     * @returns {Reference} reference
466
     */
467
    resolve(ident) {
468
        let ref, i, iz;
469
470
        assert(this.__isClosed(), "Scope should be closed.");
471
        assert(ident.type === Syntax.Identifier, "Target should be identifier.");
472
        for (i = 0, iz = this.references.length; i < iz; ++i) {
473
            ref = this.references[i];
474
            if (ref.identifier === ident) {
475
                return ref;
476
            }
477
        }
478
        return null;
479
    }
480
481
    /**
482
     * returns this scope is static
483
     * @method Scope#isStatic
484
     * @returns {boolean} static
485
     */
486
    isStatic() {
487
        return !this.dynamic;
488
    }
489
490
    /**
491
     * returns this scope has materialized arguments
492
     * @method Scope#isArgumentsMaterialized
493
     * @returns {boolean} arguemnts materialized
494
     */
495
    isArgumentsMaterialized() { // eslint-disable-line class-methods-use-this
496
        return true;
497
    }
498
499
    /**
500
     * returns this scope has materialized `this` reference
501
     * @method Scope#isThisMaterialized
502
     * @returns {boolean} this materialized
503
     */
504
    isThisMaterialized() { // eslint-disable-line class-methods-use-this
505
        return true;
506
    }
507
508
    isUsedName(name) {
509
        if (this.set.has(name)) {
510
            return true;
511
        }
512
        for (let i = 0, iz = this.through.length; i < iz; ++i) {
513
            if (this.through[i].identifier.name === name) {
514
                return true;
515
            }
516
        }
517
        return false;
518
    }
519
}
520
521
class GlobalScope extends Scope {
522
    constructor(scopeManager, block) {
523
        super(scopeManager, "global", null, block, false);
524
        this.implicit = {
525
            set: new Map(),
526
            variables: [],
527
528
            /**
529
            * List of {@link Reference}s that are left to be resolved (i.e. which
530
            * need to be linked to the variable they refer to).
531
            * @member {Reference[]} Scope#implicit#left
532
            */
533
            left: []
534
        };
535
    }
536
537
    __close(scopeManager) {
538
        const implicit = [];
539
540
        for (let i = 0, iz = this.__left.length; i < iz; ++i) {
541
            const ref = this.__left[i];
542
543
            if (ref.__maybeImplicitGlobal && !this.set.has(ref.identifier.name)) {
544
                implicit.push(ref.__maybeImplicitGlobal);
545
            }
546
        }
547
548
        // create an implicit global variable from assignment expression
549
        for (let i = 0, iz = implicit.length; i < iz; ++i) {
550
            const info = implicit[i];
551
552
            this.__defineImplicit(info.pattern,
553
                    new Definition(
554
                        Variable.ImplicitGlobalVariable,
555
                        info.pattern,
556
                        info.node,
557
                        null,
558
                        null,
559
                        null
560
                    ));
561
562
        }
563
564
        this.implicit.left = this.__left;
565
566
        return super.__close(scopeManager);
567
    }
568
569
    __defineImplicit(node, def) {
570
        if (node && node.type === Syntax.Identifier) {
571
            this.__defineGeneric(
572
                    node.name,
573
                    this.implicit.set,
574
                    this.implicit.variables,
575
                    node,
576
                    def);
577
        }
578
    }
579
}
580
581
class ModuleScope extends Scope {
582
    constructor(scopeManager, upperScope, block) {
583
        super(scopeManager, "module", upperScope, block, false);
584
    }
585
}
586
587
class FunctionExpressionNameScope extends Scope {
588
    constructor(scopeManager, upperScope, block) {
589
        super(scopeManager, "function-expression-name", upperScope, block, false);
590
        this.__define(block.id,
591
                new Definition(
592
                    Variable.FunctionName,
593
                    block.id,
594
                    block,
595
                    null,
596
                    null,
597
                    null
598
                ));
599
        this.functionExpressionScope = true;
600
    }
601
}
602
603
class CatchScope extends Scope {
604
    constructor(scopeManager, upperScope, block) {
605
        super(scopeManager, "catch", upperScope, block, false);
606
    }
607
}
608
609
class WithScope extends Scope {
610
    constructor(scopeManager, upperScope, block) {
611
        super(scopeManager, "with", upperScope, block, false);
612
    }
613
614
    __close(scopeManager) {
615
        if (this.__shouldStaticallyClose(scopeManager)) {
616
            return super.__close(scopeManager);
617
        }
618
619
        for (let i = 0, iz = this.__left.length; i < iz; ++i) {
620
            const ref = this.__left[i];
621
622
            ref.tainted = true;
623
            this.__delegateToUpperScope(ref);
624
        }
625
        this.__left = null;
626
627
        return this.upper;
628
    }
629
}
630
631
class BlockScope extends Scope {
632
    constructor(scopeManager, upperScope, block) {
633
        super(scopeManager, "block", upperScope, block, false);
634
    }
635
}
636
637
class SwitchScope extends Scope {
638
    constructor(scopeManager, upperScope, block) {
639
        super(scopeManager, "switch", upperScope, block, false);
640
    }
641
}
642
643
class FunctionScope extends Scope {
644
    constructor(scopeManager, upperScope, block, isMethodDefinition) {
645
        super(scopeManager, "function", upperScope, block, isMethodDefinition);
646
647
        // section 9.2.13, FunctionDeclarationInstantiation.
648
        // NOTE Arrow functions never have an arguments objects.
649
        if (this.block.type !== Syntax.ArrowFunctionExpression) {
650
            this.__defineArguments();
651
        }
652
    }
653
654
    isArgumentsMaterialized() {
655
656
        // TODO(Constellation)
657
        // We can more aggressive on this condition like this.
658
        //
659
        // function t() {
660
        //     // arguments of t is always hidden.
661
        //     function arguments() {
662
        //     }
663
        // }
664
        if (this.block.type === Syntax.ArrowFunctionExpression) {
665
            return false;
666
        }
667
668
        if (!this.isStatic()) {
669
            return true;
670
        }
671
672
        const variable = this.set.get("arguments");
673
674
        assert(variable, "Always have arguments variable.");
675
        return variable.tainted || variable.references.length !== 0;
676
    }
677
678
    isThisMaterialized() {
679
        if (!this.isStatic()) {
680
            return true;
681
        }
682
        return this.thisFound;
683
    }
684
685
    __defineArguments() {
686
        this.__defineGeneric(
687
                "arguments",
688
                this.set,
689
                this.variables,
690
                null,
691
                null);
692
        this.taints.set("arguments", true);
693
    }
694
695
    // References in default parameters isn't resolved to variables which are in their function body.
696
    //     const x = 1
697
    //     function f(a = x) { // This `x` is resolved to the `x` in the outer scope.
698
    //         const x = 2
699
    //         console.log(a)
700
    //     }
701
    __isValidResolution(ref, variable) {
702
703
        // If `options.nodejsScope` is true, `this.block` becomes a Program node.
704
        if (this.block.type === "Program") {
705
            return true;
706
        }
707
708
        const bodyStart = this.block.body.range[0];
709
710
        // It's invalid resolution in the following case:
711
        return !(
712
            variable.scope === this &&
713
            ref.identifier.range[0] < bodyStart &&                 // the reference is in the parameter part.
714
            variable.defs.every(d => d.name.range[0] >= bodyStart) // the variable is in the body.
715
        );
716
    }
717
}
718
719
class ForScope extends Scope {
720
    constructor(scopeManager, upperScope, block) {
721
        super(scopeManager, "for", upperScope, block, false);
722
    }
723
}
724
725
class ClassScope extends Scope {
726
    constructor(scopeManager, upperScope, block) {
727
        super(scopeManager, "class", upperScope, block, false);
728
    }
729
}
730
731
module.exports = {
732
    Scope,
733
    GlobalScope,
734
    ModuleScope,
735
    FunctionExpressionNameScope,
736
    CatchScope,
737
    WithScope,
738
    BlockScope,
739
    SwitchScope,
740
    FunctionScope,
741
    ForScope,
742
    ClassScope
743
};
744
745
/* vim: set sw=4 ts=4 et tw=80 : */