# HG changeset patch # User sundar # Date 1509031639 -19800 # Node ID 217860329f71afcdd129b4e256fa627c41db18c3 # Parent 9c1e4b0a47614ec7bcf021e2b747e27a707e122d 8190217: Add a JS "static checker" sample for nashorn parser API Reviewed-by: jlaskey diff -r 9c1e4b0a4761 -r 217860329f71 src/sample/nashorn/bad_patterns.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/sample/nashorn/bad_patterns.js Thu Oct 26 20:57:19 2017 +0530 @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Oracle nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * This is a test script file for staticchecker.js. + * + * Usage: + * jjs --language=es6 staticcheker.js -- bad_patterns.js + */ +var obj = {} +obj.__proto__ = null; + +with(obj) {} + +delete obj; + +eval("print('hello')") + +Object = null +JavaImporter = undefined + +function func() {} + +func.prototype.x = 44; + +Object.prototype.foo = "hello"; + +String.prototype.bar = function() {} + +try { + eval("***"); +} catch(e) {} + +try { + eval("***"); +} catch(e) { ; } + +try { + eval("***"); +} catch(e) { print(e) } diff -r 9c1e4b0a4761 -r 217860329f71 src/sample/nashorn/staticchecker.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/sample/nashorn/staticchecker.js Thu Oct 26 20:57:19 2017 +0530 @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Oracle nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Usage: jjs --language=es6 staticchecker.js -- +// or jjs --language=es6 staticchecker.js -- +// default argument is the current directory + +if (arguments.length == 0) { + arguments[0] = "."; +} + +const File = Java.type("java.io.File"); +const file = new File(arguments[0]); +if (!file.exists()) { + print(arguments[0] + " is neither a file nor a directory"); + exit(1); +} + +// A simple static checker for javascript best practices. +// static checks performed are: +// +// * __proto__ magic property is bad (non-standard) +// * 'with' statements are bad +// * 'eval' calls are bad +// * 'delete foo' (scope variable delete) is bad +// * assignment to standard globals is bad (eg. Object = "hello") +// * assignment to property on standard prototype is bad (eg. String.prototype.foo = 45) +// * exception swallow (empty catch block in try-catch statements) + +const Files = Java.type("java.nio.file.Files"); +const EmptyStatementTree = Java.type("jdk.nashorn.api.tree.EmptyStatementTree"); +const IdentifierTree = Java.type("jdk.nashorn.api.tree.IdentifierTree"); +const MemberSelectTree = Java.type("jdk.nashorn.api.tree.MemberSelectTree"); +const Parser = Java.type("jdk.nashorn.api.tree.Parser"); +const SimpleTreeVisitor = Java.type("jdk.nashorn.api.tree.SimpleTreeVisitorES6"); +const Tree = Java.type("jdk.nashorn.api.tree.Tree"); + +const parser = Parser.create("-scripting", "--language=es6"); + +// capture standard global upfront +const globals = new Set(); +for (let name of Object.getOwnPropertyNames(this)) { + globals.add(name); +} + +const checkFile = function(file) { + print("Parsing " + file); + const ast = parser.parse(file, print); + if (!ast) { + print("FAILED to parse: " + file); + return; + } + + const checker = new (Java.extend(SimpleTreeVisitor)) { + lineMap: null, + + printWarning(node, msg) { + var pos = node.startPosition; + var line = this.lineMap.getLineNumber(pos); + var column = this.lineMap.getColumnNumber(pos); + print(`WARNING: ${msg} in ${file} @ ${line}:${column}`); + }, + + printWithWarning(node) { + this.printWarning(node, "'with' usage"); + }, + + printProtoWarning(node) { + this.printWarning(node, "__proto__ usage"); + }, + + printScopeDeleteWarning(node, varName) { + this.printWarning(node, `delete ${varName}`); + }, + + hasOnlyEmptyStats(stats) { + const itr = stats.iterator(); + while (itr.hasNext()) { + if (! (itr.next() instanceof EmptyStatementTree)) { + return false; + } + } + + return true; + }, + + checkProto(node, name) { + if (name == "__proto__") { + this.printProtoWarning(node); + } + }, + + checkAssignment(lhs) { + if (lhs instanceof IdentifierTree && globals.has(lhs.name)) { + this.printWarning(lhs, `assignment to standard global "${lhs.name}"`); + } else if (lhs instanceof MemberSelectTree) { + const expr = lhs.expression; + if (expr instanceof MemberSelectTree && + expr.expression instanceof IdentifierTree && + globals.has(expr.expression.name) && + "prototype" == expr.identifier) { + this.printWarning(lhs, + `property set "${expr.expression.name}.prototype.${lhs.identifier}"`); + } + } + }, + + visitAssignment(node, extra) { + this.checkAssignment(node.variable); + Java.super(checker).visitAssignment(node, extra); + }, + + visitCatch(node, extra) { + var stats = node.block.statements; + if (stats.empty || this.hasOnlyEmptyStats(stats)) { + this.printWarning(node, "exception swallow"); + } + Java.super(checker).visitCatch(node, extra); + }, + + visitCompilationUnit(node, extra) { + this.lineMap = node.lineMap; + Java.super(checker).visitCompilationUnit(node, extra); + }, + + visitFunctionCall(node, extra) { + var func = node.functionSelect; + if (func instanceof IdentifierTree && func.name == "eval") { + this.printWarning(node, "eval call found"); + } + Java.super(checker).visitFunctionCall(node, extra); + }, + + visitIdentifier(node, extra) { + this.checkProto(node, node.name); + Java.super(checker).visitIdentifier(node, extra); + }, + + visitMemberSelect(node, extra) { + this.checkProto(node, node.identifier); + Java.super(checker).visitMemberSelect(node, extra); + }, + + visitProperty(node, extra) { + this.checkProto(node, node.key); + Java.super(checker).visitProperty(node, extra); + }, + + visitUnary(node, extra) { + if (node.kind == Tree.Kind.DELETE && + node.expression instanceof IdentifierTree) { + this.printScopeDeleteWarning(node, node.expression.name); + } + Java.super(checker).visitUnary(node, extra); + }, + + visitWith(node, extra) { + this.printWithWarning(node); + Java.super(checker).visitWith(node, extra); + } + }; + + try { + ast.accept(checker, null); + } catch (e) { + print(e); + if (e.printStackTrace) e.printStackTrace(); + if (e.stack) print(e.stack); + } +} + +if (file.isDirectory()) { + Files.walk(file.toPath()) + .filter(function(p) Files.isRegularFile(p)) + .filter(function(p) p.toFile().name.endsWith('.js')) + .forEach(checkFile); +} else { + checkFile(file); +}