8032068: implement @sourceURL and #sourceURL directives
authorsundar
Mon, 20 Jan 2014 19:51:54 +0530
changeset 22390 1d2d88e478ea
parent 22389 ea3dda90768c
child 22411 cdbd053d435f
8032068: implement @sourceURL and #sourceURL directives Reviewed-by: hannesw, lagergren
nashorn/src/jdk/nashorn/internal/codegen/Compiler.java
nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java
nashorn/src/jdk/nashorn/internal/parser/AbstractParser.java
nashorn/src/jdk/nashorn/internal/parser/Lexer.java
nashorn/src/jdk/nashorn/internal/parser/Parser.java
nashorn/src/jdk/nashorn/internal/parser/TokenType.java
nashorn/test/script/basic/JDK-8032068.js
nashorn/test/script/basic/JDK-8032068.js.EXPECTED
--- a/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java	Fri Jan 17 20:09:47 2014 +0530
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java	Mon Jan 20 19:51:54 2014 +0530
@@ -85,6 +85,8 @@
 
     private Source source;
 
+    private String sourceName;
+
     private final Map<String, byte[]> bytecode;
 
     private final Set<CompileUnit> compileUnits;
@@ -267,6 +269,7 @@
                 append('$').
                 append(safeSourceName(functionNode.getSource()));
         this.source = functionNode.getSource();
+        this.sourceName = functionNode.getSourceName();
         this.scriptName = sb.toString();
     }
 
@@ -573,7 +576,7 @@
     }
 
     private CompileUnit initCompileUnit(final String unitClassName, final long initialWeight) {
-        final ClassEmitter classEmitter = new ClassEmitter(env, source.getName(), unitClassName, strict);
+        final ClassEmitter classEmitter = new ClassEmitter(env, sourceName, unitClassName, strict);
         final CompileUnit  compileUnit  = new CompileUnit(unitClassName, classEmitter, initialWeight);
 
         classEmitter.begin();
--- a/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java	Fri Jan 17 20:09:47 2014 +0530
+++ b/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java	Mon Jan 20 19:51:54 2014 +0530
@@ -29,6 +29,7 @@
 import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import jdk.nashorn.internal.codegen.CompileUnit;
 import jdk.nashorn.internal.codegen.Compiler;
@@ -138,6 +139,9 @@
     /** Function flags. */
     private final int flags;
 
+    /** //@ sourceURL or //# sourceURL for program function nodes */
+    private final String sourceURL;
+
     private final int lineNumber;
 
     /** Is anonymous function flag. */
@@ -223,6 +227,7 @@
      * @param parameters parameter list
      * @param kind       kind of function as in {@link FunctionNode.Kind}
      * @param flags      initial flags
+     * @param sourceURL  sourceURL specified in script (optional)
      */
     public FunctionNode(
         final Source source,
@@ -235,7 +240,8 @@
         final String name,
         final List<IdentNode> parameters,
         final FunctionNode.Kind kind,
-        final int flags) {
+        final int flags,
+        final String sourceURL) {
         super(token, finish);
 
         this.source           = source;
@@ -250,6 +256,7 @@
         this.compilationState = EnumSet.of(CompilationState.INITIALIZED);
         this.declaredSymbols  = new HashSet<>();
         this.flags            = flags;
+        this.sourceURL        = sourceURL;
         this.compileUnit      = null;
         this.body             = null;
         this.snapshot         = null;
@@ -260,6 +267,7 @@
         final FunctionNode functionNode,
         final long lastToken,
         final int flags,
+        final String sourceURL,
         final String name,
         final Type returnType,
         final CompileUnit compileUnit,
@@ -271,6 +279,7 @@
         super(functionNode);
         this.lineNumber       = functionNode.lineNumber;
         this.flags            = flags;
+        this.sourceURL        = sourceURL;
         this.name             = name;
         this.returnType       = returnType;
         this.compileUnit      = compileUnit;
@@ -308,6 +317,38 @@
     }
 
     /**
+     * get source name - sourceURL or name derived from Source.
+     *
+     * @return name for the script source
+     */
+    public String getSourceName() {
+        return (sourceURL != null)? sourceURL : source.getName();
+    }
+
+    /**
+     * get the sourceURL
+     * @return the sourceURL
+     */
+    public String getSourceURL() {
+        return sourceURL;
+    }
+
+    /**
+     * Set the sourceURL
+     *
+     * @param lc lexical context
+     * @param newSourceURL source url string to set
+     * @return function node or a new one if state was changed
+     */
+    public FunctionNode setSourceURL(final LexicalContext lc, final String newSourceURL) {
+        if (Objects.equals(sourceURL, newSourceURL)) {
+            return this;
+        }
+
+        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, newSourceURL, name, returnType, compileUnit, compilationState, body, parameters, null, hints));
+    }
+
+    /**
      * Returns the line number.
      * @return the line number.
      */
@@ -335,7 +376,7 @@
         if (this.snapshot == null) {
             return this;
         }
-        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, name, returnType, compileUnit, compilationState, body, parameters, null, hints));
+        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, null, hints));
     }
 
     /**
@@ -351,7 +392,7 @@
         if (isProgram() || parameters.isEmpty()) {
             return this; //never specialize anything that won't be recompiled
         }
-        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, name, returnType, compileUnit, compilationState, body, parameters, this, hints));
+        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, this, hints));
     }
 
     /**
@@ -409,7 +450,7 @@
         }
         final EnumSet<CompilationState> newState = EnumSet.copyOf(this.compilationState);
         newState.add(state);
-        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, name, returnType, compileUnit, newState, body, parameters, snapshot, hints));
+        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, newState, body, parameters, snapshot, hints));
     }
 
     /**
@@ -430,7 +471,7 @@
         if (this.hints == hints) {
             return this;
         }
-        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
+        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
     }
 
     /**
@@ -483,7 +524,7 @@
         if (this.flags == flags) {
             return this;
         }
-        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
+        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
     }
 
     @Override
@@ -593,7 +634,7 @@
         if(this.body == body) {
             return this;
         }
-        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags | (body.needsScope() ? FunctionNode.HAS_SCOPE_BLOCK : 0), name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
+        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags | (body.needsScope() ? FunctionNode.HAS_SCOPE_BLOCK : 0), sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
     }
 
     /**
@@ -688,7 +729,7 @@
         if (this.lastToken == lastToken) {
             return this;
         }
-        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
+        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
     }
 
     /**
@@ -710,7 +751,7 @@
         if (this.name.equals(name)) {
             return this;
         }
-        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
+        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
     }
 
     /**
@@ -760,7 +801,7 @@
         if (this.parameters == parameters) {
             return this;
         }
-        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
+        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
     }
 
     /**
@@ -825,6 +866,7 @@
                 this,
                 lastToken,
                 flags,
+                sourceURL,
                 name,
                 type,
                 compileUnit,
@@ -863,7 +905,7 @@
         if (this.compileUnit == compileUnit) {
             return this;
         }
-        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
+        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
     }
 
     /**
--- a/nashorn/src/jdk/nashorn/internal/parser/AbstractParser.java	Fri Jan 17 20:09:47 2014 +0530
+++ b/nashorn/src/jdk/nashorn/internal/parser/AbstractParser.java	Mon Jan 20 19:51:54 2014 +0530
@@ -26,6 +26,7 @@
 package jdk.nashorn.internal.parser;
 
 import static jdk.nashorn.internal.parser.TokenType.COMMENT;
+import static jdk.nashorn.internal.parser.TokenType.DIRECTIVE_COMMENT;
 import static jdk.nashorn.internal.parser.TokenType.EOF;
 import static jdk.nashorn.internal.parser.TokenType.EOL;
 import static jdk.nashorn.internal.parser.TokenType.IDENT;
@@ -84,6 +85,9 @@
     /** Is this parser running under strict mode? */
     protected boolean isStrictMode;
 
+    /** //@ sourceURL or //# sourceURL */
+    protected String sourceURL;
+
     /**
      * Construct a parser.
      *
@@ -156,17 +160,38 @@
     protected final TokenType nextOrEOL() {
         do {
             nextToken();
-        } while (type == COMMENT);
+            if (type == DIRECTIVE_COMMENT) {
+                checkDirectiveComment();
+            }
+        } while (type == COMMENT || type == DIRECTIVE_COMMENT);
 
         return type;
     }
 
+    // sourceURL= after directive comment
+    private static final String SOURCE_URL_PREFIX = "sourceURL=";
+
+    // currently only @sourceURL=foo supported
+    private void checkDirectiveComment() {
+        // if already set, ignore this one
+        if (sourceURL != null) {
+            return;
+        }
+
+        final String comment = (String) lexer.getValueOf(token, isStrictMode);
+        final int len = comment.length();
+        // 4 characters for directive comment marker //@\s or //#\s
+        if (len > 4 && comment.substring(4).startsWith(SOURCE_URL_PREFIX)) {
+            sourceURL = comment.substring(4 + SOURCE_URL_PREFIX.length());
+        }
+    }
+
     /**
      * Seek next token.
      *
      * @return tokenType of next token.
      */
-    private final TokenType nextToken() {
+    private TokenType nextToken() {
         // Capture last token tokenType.
         last = type;
         if (type != EOF) {
--- a/nashorn/src/jdk/nashorn/internal/parser/Lexer.java	Fri Jan 17 20:09:47 2014 +0530
+++ b/nashorn/src/jdk/nashorn/internal/parser/Lexer.java	Mon Jan 20 19:51:54 2014 +0530
@@ -27,6 +27,7 @@
 
 import static jdk.nashorn.internal.parser.TokenType.ADD;
 import static jdk.nashorn.internal.parser.TokenType.COMMENT;
+import static jdk.nashorn.internal.parser.TokenType.DIRECTIVE_COMMENT;
 import static jdk.nashorn.internal.parser.TokenType.DECIMAL;
 import static jdk.nashorn.internal.parser.TokenType.EOF;
 import static jdk.nashorn.internal.parser.TokenType.EOL;
@@ -434,12 +435,18 @@
             if (ch1 == '/') {
                 // Skip over //.
                 skip(2);
+
+                boolean directiveComment = false;
+                if ((ch0 == '#' || ch0 == '@') && (ch1 == ' ')) {
+                    directiveComment = true;
+                }
+
                 // Scan for EOL.
                 while (!atEOF() && !isEOL(ch0)) {
                     skip(1);
                 }
                 // Did detect a comment.
-                add(COMMENT, start);
+                add(directiveComment? DIRECTIVE_COMMENT : COMMENT, start);
                 return true;
             } else if (ch1 == '*') {
                 // Skip over /*.
@@ -1623,6 +1630,8 @@
             return valueOfPattern(start, len); // RegexToken::LexerToken
         case XML:
             return valueOfXML(start, len); // XMLToken::LexerToken
+        case DIRECTIVE_COMMENT:
+            return source.getString(start, len);
         default:
             break;
         }
--- a/nashorn/src/jdk/nashorn/internal/parser/Parser.java	Fri Jan 17 20:09:47 2014 +0530
+++ b/nashorn/src/jdk/nashorn/internal/parser/Parser.java	Mon Jan 20 19:51:54 2014 +0530
@@ -421,7 +421,8 @@
                 name,
                 parameters,
                 kind,
-                flags);
+                flags,
+                sourceURL);
 
         lc.push(functionNode);
         // Create new block, and just put it on the context stack, restoreFunctionNode() will associate it with the
@@ -640,6 +641,10 @@
 
         script = restoreFunctionNode(script, token); //commit code
         script = script.setBody(lc, script.getBody().setNeedsScope(lc));
+        // user may have directive comment to set sourceURL
+        if (sourceURL != null) {
+            script = script.setSourceURL(lc, sourceURL);
+        }
 
         return script;
     }
--- a/nashorn/src/jdk/nashorn/internal/parser/TokenType.java	Fri Jan 17 20:09:47 2014 +0530
+++ b/nashorn/src/jdk/nashorn/internal/parser/TokenType.java	Mon Jan 20 19:51:54 2014 +0530
@@ -41,10 +41,14 @@
  */
 @SuppressWarnings("javadoc")
 public enum TokenType {
-    ERROR          (SPECIAL,  null),
-    EOF            (SPECIAL,  null),
-    EOL            (SPECIAL,  null),
-    COMMENT        (SPECIAL,  null),
+    ERROR                (SPECIAL,  null),
+    EOF                  (SPECIAL,  null),
+    EOL                  (SPECIAL,  null),
+    COMMENT              (SPECIAL,  null),
+    // comments of the form //@ foo=bar or //# foo=bar
+    // These comments are treated as special instructions
+    // to the lexer, parser or codegenerator.
+    DIRECTIVE_COMMENT    (SPECIAL,  null),
 
     NOT            (UNARY,   "!",    14, false),
     NE             (BINARY,  "!=",    9, true),
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8032068.js	Mon Jan 20 19:51:54 2014 +0530
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ * 
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ * 
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ * 
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * JDK-8032068: implement @sourceURL and #sourceURL directives.
+ *
+ * @test
+ * @run
+ */
+
+
+try {
+    Function("throw new Error();\n//# sourceURL=foo.js")();
+} catch (e) {
+    print(e.stack.replace(/\\/g, '/'));
+}
+
+try {
+    eval("function g() { throw Error('x');\n } g();\n//# sourceURL=bar.js");
+} catch (e) {
+    print(e.stack.replace(/\\/g, '/'));
+}
+
+// check @sourceURL for compatibility
+try {
+    Function("throw new Error();\n//@ sourceURL=foo2.js")();
+} catch (e) {
+    print(e.stack.replace(/\\/g, '/'));
+}
+
+try {
+    eval("function g() { throw Error('x');\n } g();\n//@ sourceURL=bar2.js");
+} catch (e) {
+    print(e.stack.replace(/\\/g, '/'));
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8032068.js.EXPECTED	Mon Jan 20 19:51:54 2014 +0530
@@ -0,0 +1,14 @@
+Error
+	at <anonymous> (foo.js:2)
+	at <program> (test/script/basic/JDK-8032068.js:33)
+Error: x
+	at g (bar.js:1)
+	at <program> (bar.js:2)
+	at <program> (test/script/basic/JDK-8032068.js:39)
+Error
+	at <anonymous> (foo2.js:2)
+	at <program> (test/script/basic/JDK-8032068.js:46)
+Error: x
+	at g (bar2.js:1)
+	at <program> (bar2.js:2)
+	at <program> (test/script/basic/JDK-8032068.js:52)