src/sample/nashorn/staticchecker.js
changeset 47457 217860329f71
equal deleted inserted replaced
47456:9c1e4b0a4761 47457:217860329f71
       
     1 /*
       
     2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
       
     3  *
       
     4  * Redistribution and use in source and binary forms, with or without
       
     5  * modification, are permitted provided that the following conditions
       
     6  * are met:
       
     7  *
       
     8  *   - Redistributions of source code must retain the above copyright
       
     9  *     notice, this list of conditions and the following disclaimer.
       
    10  *
       
    11  *   - Redistributions in binary form must reproduce the above copyright
       
    12  *     notice, this list of conditions and the following disclaimer in the
       
    13  *     documentation and/or other materials provided with the distribution.
       
    14  *
       
    15  *   - Neither the name of Oracle nor the names of its
       
    16  *     contributors may be used to endorse or promote products derived
       
    17  *     from this software without specific prior written permission.
       
    18  *
       
    19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
       
    20  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
       
    21  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
       
    22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
       
    23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
       
    24  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
       
    25  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
       
    26  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
       
    27  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
       
    28  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
       
    29  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       
    30  */
       
    31 
       
    32 // Usage: jjs --language=es6 staticchecker.js -- <file>
       
    33 //    or  jjs --language=es6 staticchecker.js -- <directory>
       
    34 // default argument is the current directory
       
    35 
       
    36 if (arguments.length == 0) {
       
    37     arguments[0] = ".";
       
    38 }
       
    39 
       
    40 const File = Java.type("java.io.File");
       
    41 const file = new File(arguments[0]);
       
    42 if (!file.exists()) {
       
    43     print(arguments[0] + " is neither a file nor a directory");
       
    44     exit(1);
       
    45 }
       
    46 
       
    47 // A simple static checker for javascript best practices.
       
    48 // static checks performed are:
       
    49 //
       
    50 // *  __proto__ magic property is bad (non-standard)
       
    51 // * 'with' statements are bad
       
    52 // * 'eval' calls are bad
       
    53 // * 'delete foo' (scope variable delete) is bad
       
    54 // * assignment to standard globals is bad (eg. Object = "hello")
       
    55 // * assignment to property on standard prototype is bad (eg. String.prototype.foo = 45)
       
    56 // * exception swallow (empty catch block in try-catch statements)
       
    57 
       
    58 const Files = Java.type("java.nio.file.Files");
       
    59 const EmptyStatementTree = Java.type("jdk.nashorn.api.tree.EmptyStatementTree");
       
    60 const IdentifierTree = Java.type("jdk.nashorn.api.tree.IdentifierTree");
       
    61 const MemberSelectTree = Java.type("jdk.nashorn.api.tree.MemberSelectTree");
       
    62 const Parser = Java.type("jdk.nashorn.api.tree.Parser");
       
    63 const SimpleTreeVisitor = Java.type("jdk.nashorn.api.tree.SimpleTreeVisitorES6");
       
    64 const Tree = Java.type("jdk.nashorn.api.tree.Tree");
       
    65 
       
    66 const parser = Parser.create("-scripting", "--language=es6");
       
    67 
       
    68 // capture standard global upfront
       
    69 const globals = new Set();
       
    70 for (let name of Object.getOwnPropertyNames(this)) {
       
    71     globals.add(name);
       
    72 }
       
    73 
       
    74 const checkFile = function(file) {
       
    75     print("Parsing " + file);
       
    76     const ast = parser.parse(file, print);
       
    77     if (!ast) {
       
    78         print("FAILED to parse: " + file);
       
    79         return;
       
    80     }
       
    81 
       
    82     const checker = new (Java.extend(SimpleTreeVisitor)) {
       
    83         lineMap: null,
       
    84 
       
    85         printWarning(node, msg) {
       
    86             var pos = node.startPosition;
       
    87             var line = this.lineMap.getLineNumber(pos);
       
    88             var column = this.lineMap.getColumnNumber(pos);
       
    89             print(`WARNING: ${msg} in ${file} @ ${line}:${column}`);
       
    90         },
       
    91         
       
    92         printWithWarning(node) {
       
    93             this.printWarning(node, "'with' usage");
       
    94         },
       
    95 
       
    96         printProtoWarning(node) {
       
    97             this.printWarning(node, "__proto__ usage");
       
    98         },
       
    99 
       
   100         printScopeDeleteWarning(node, varName) {
       
   101             this.printWarning(node, `delete ${varName}`);
       
   102         },
       
   103 
       
   104         hasOnlyEmptyStats(stats) {
       
   105             const itr = stats.iterator();
       
   106             while (itr.hasNext()) {
       
   107                 if (! (itr.next() instanceof EmptyStatementTree)) {
       
   108                     return false;
       
   109                 }
       
   110             }
       
   111 
       
   112             return true;
       
   113         },
       
   114 
       
   115         checkProto(node, name) {
       
   116             if (name == "__proto__") {
       
   117                 this.printProtoWarning(node);
       
   118             }
       
   119         },
       
   120 
       
   121         checkAssignment(lhs) {
       
   122             if (lhs instanceof IdentifierTree && globals.has(lhs.name)) {
       
   123                 this.printWarning(lhs, `assignment to standard global "${lhs.name}"`);
       
   124             } else if (lhs instanceof MemberSelectTree) {
       
   125                 const expr = lhs.expression;
       
   126                 if (expr instanceof MemberSelectTree &&
       
   127                     expr.expression instanceof IdentifierTree &&
       
   128                     globals.has(expr.expression.name) && 
       
   129                     "prototype" == expr.identifier) {
       
   130                     this.printWarning(lhs, 
       
   131                         `property set "${expr.expression.name}.prototype.${lhs.identifier}"`);
       
   132                 }
       
   133             }
       
   134         },
       
   135 
       
   136         visitAssignment(node, extra) {
       
   137             this.checkAssignment(node.variable);
       
   138             Java.super(checker).visitAssignment(node, extra);
       
   139         },
       
   140 
       
   141         visitCatch(node, extra) {
       
   142             var stats = node.block.statements;
       
   143             if (stats.empty || this.hasOnlyEmptyStats(stats)) {
       
   144                 this.printWarning(node, "exception swallow");
       
   145             }
       
   146             Java.super(checker).visitCatch(node, extra);
       
   147         },
       
   148 
       
   149         visitCompilationUnit(node, extra) {
       
   150             this.lineMap = node.lineMap;
       
   151             Java.super(checker).visitCompilationUnit(node, extra);
       
   152         },
       
   153 
       
   154         visitFunctionCall(node, extra) {
       
   155            var func = node.functionSelect;
       
   156            if (func instanceof IdentifierTree && func.name == "eval") {
       
   157                this.printWarning(node, "eval call found");
       
   158            }
       
   159            Java.super(checker).visitFunctionCall(node, extra);
       
   160         },
       
   161 
       
   162         visitIdentifier(node, extra) {
       
   163             this.checkProto(node, node.name);
       
   164             Java.super(checker).visitIdentifier(node, extra);
       
   165         },
       
   166 
       
   167         visitMemberSelect(node, extra) {
       
   168             this.checkProto(node, node.identifier);
       
   169             Java.super(checker).visitMemberSelect(node, extra);
       
   170         },
       
   171 
       
   172         visitProperty(node, extra) {
       
   173             this.checkProto(node, node.key);
       
   174             Java.super(checker).visitProperty(node, extra);
       
   175         },
       
   176 
       
   177         visitUnary(node, extra) {
       
   178             if (node.kind == Tree.Kind.DELETE &&
       
   179                 node.expression instanceof IdentifierTree) {
       
   180                 this.printScopeDeleteWarning(node, node.expression.name);
       
   181             }
       
   182             Java.super(checker).visitUnary(node, extra);
       
   183         },
       
   184 
       
   185         visitWith(node, extra) {
       
   186             this.printWithWarning(node);
       
   187             Java.super(checker).visitWith(node, extra);
       
   188         }
       
   189     };
       
   190 
       
   191     try {
       
   192         ast.accept(checker, null);
       
   193     } catch (e) {
       
   194         print(e);
       
   195         if (e.printStackTrace) e.printStackTrace();
       
   196         if (e.stack) print(e.stack);
       
   197     }
       
   198 }
       
   199 
       
   200 if (file.isDirectory()) {
       
   201     Files.walk(file.toPath())
       
   202         .filter(function(p) Files.isRegularFile(p))
       
   203         .filter(function(p) p.toFile().name.endsWith('.js'))
       
   204         .forEach(checkFile);
       
   205 } else {
       
   206     checkFile(file);
       
   207 }