Bug 601262 - A string literal containing an octal escape before a strict mode directive should be a syntax error. (Yes, this requirement is ugly, but it's what ES5 says.) r=brendan
authorJeff Walden <jwalden@mit.edu>
Wed, 13 Oct 2010 04:00:28 -0700
changeset 59219 3fe1860a15b35fc08e732ae8dc8a17d22b736015
parent 59218 89a4b9b69ae5934a43dc34cfc9db49f5a6140b5e
child 59220 be1532afeb632c6ad389072969627df92f4da8ef
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
reviewersbrendan
bugs601262
milestone2.0b8pre
Bug 601262 - A string literal containing an octal escape before a strict mode directive should be a syntax error. (Yes, this requirement is ugly, but it's what ES5 says.) r=brendan
js/src/jsparse.cpp
js/src/jsparse.h
js/src/jsscan.cpp
js/src/jsscan.h
js/src/tests/ecma_5/strict/directive-prologue-01.js
js/src/tests/ecma_5/strict/jstests.list
--- a/js/src/jsparse.cpp
+++ b/js/src/jsparse.cpp
@@ -860,32 +860,33 @@ Compiler::compileScript(JSContext *cx, J
 
 #if JS_HAS_XML_SUPPORT
     pn = NULL;
     bool onlyXML;
     onlyXML = true;
 #endif
 
     inDirectivePrologue = true;
+    tokenStream.setOctalCharacterEscape(false);
     for (;;) {
         tt = tokenStream.peekToken(TSF_OPERAND);
         if (tt <= TOK_EOF) {
             if (tt == TOK_EOF)
                 break;
             JS_ASSERT(tt == TOK_ERROR);
             goto out;
         }
 
         pn = parser.statement();
         if (!pn)
             goto out;
         JS_ASSERT(!cg.blockNode);
 
-        if (inDirectivePrologue)
-            inDirectivePrologue = parser.recognizeDirectivePrologue(pn);
+        if (inDirectivePrologue && !parser.recognizeDirectivePrologue(pn, &inDirectivePrologue))
+            goto out;
 
         if (!js_FoldConstants(cx, pn, &cg))
             goto out;
 
         if (cg.functionList) {
             if (!parser.analyzeFunctions(cg.functionList, cg.flags))
                 goto out;
             cg.functionList = NULL;
@@ -3220,23 +3221,43 @@ Parser::functionExpr()
  *   "use\x20loose"
  *   "use strict"
  * }
  *
  * That is, a statement can be a Directive Prologue member, even
  * if it can't possibly be a directive, now or in the future.
  */
 bool
-Parser::recognizeDirectivePrologue(JSParseNode *pn)
-{
-    if (!pn->isDirectivePrologueMember())
-        return false;
+Parser::recognizeDirectivePrologue(JSParseNode *pn, bool *isDirectivePrologueMember)
+{
+    *isDirectivePrologueMember = pn->isDirectivePrologueMember();
+    if (!*isDirectivePrologueMember)
+        return true;
     if (pn->isDirective()) {
         JSAtom *directive = pn->pn_kid->pn_atom;
         if (directive == context->runtime->atomState.useStrictAtom) {
+            /*
+             * Unfortunately, Directive Prologue members in general may contain
+             * escapes, even while "use strict" directives may not.  Therefore
+             * we must check whether an octal character escape has been seen in
+             * any previous directives whenever we encounter a "use strict"
+             * directive, so that the octal escape is properly treated as a
+             * syntax error.  An example of this case:
+             *
+             *   function error()
+             *   {
+             *     "\145"; // octal escape
+             *     "use strict"; // retroactively makes "\145" a syntax error
+             *   }
+             */
+            if (tokenStream.hasOctalCharacterEscape()) {
+                reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_DEPRECATED_OCTAL);
+                return false;
+            }
+
             tc->flags |= TCF_STRICT_MODE_CODE;
             tokenStream.setStrictMode();
         }
     }
     return true;
 }
 
 /*
@@ -3244,29 +3265,30 @@ Parser::recognizeDirectivePrologue(JSPar
  * statements' trees.  If called from block-parsing code, the caller must
  * match { before and } after.
  */
 JSParseNode *
 Parser::statements()
 {
     JSParseNode *pn, *pn2, *saveBlock;
     TokenKind tt;
-    bool inDirectivePrologue = tc->atTopLevel();
 
     JS_CHECK_RECURSION(context, return NULL);
 
     pn = ListNode::create(tc);
     if (!pn)
         return NULL;
     pn->pn_type = TOK_LC;
     pn->makeEmpty();
     pn->pn_blockid = tc->blockid();
     saveBlock = tc->blockNode;
     tc->blockNode = pn;
 
+    bool inDirectivePrologue = tc->atTopLevel();
+    tokenStream.setOctalCharacterEscape(false);
     for (;;) {
         tt = tokenStream.peekToken(TSF_OPERAND);
         if (tt <= TOK_EOF || tt == TOK_RC) {
             if (tt == TOK_ERROR) {
                 if (tokenStream.isEOF())
                     tokenStream.setUnexpectedEOF();
                 return NULL;
             }
@@ -3274,18 +3296,18 @@ Parser::statements()
         }
         pn2 = statement();
         if (!pn2) {
             if (tokenStream.isEOF())
                 tokenStream.setUnexpectedEOF();
             return NULL;
         }
 
-        if (inDirectivePrologue)
-            inDirectivePrologue = recognizeDirectivePrologue(pn2);
+        if (inDirectivePrologue && !recognizeDirectivePrologue(pn2, &inDirectivePrologue))
+            return NULL;
 
         if (pn2->pn_type == TOK_FUNCTION) {
             /*
              * PNX_FUNCDEFS notifies the emitter that the block contains top-
              * level function definitions that should be processed before the
              * rest of nodes.
              *
              * TCF_HAS_FUNCTION_STMT is for the TOK_LC case in Statement. It
--- a/js/src/jsparse.h
+++ b/js/src/jsparse.h
@@ -1113,17 +1113,17 @@ private:
     JSParseNode *unaryExpr();
     JSParseNode *memberExpr(JSBool allowCallSyntax);
     JSParseNode *primaryExpr(js::TokenKind tt, JSBool afterDot);
     JSParseNode *parenExpr(JSParseNode *pn1, JSBool *genexp);
 
     /*
      * Additional JS parsers.
      */
-    bool recognizeDirectivePrologue(JSParseNode *pn);
+    bool recognizeDirectivePrologue(JSParseNode *pn, bool *isDirectivePrologueMember);
 
     enum FunctionType { GETTER, SETTER, GENERAL };
     bool functionArguments(JSTreeContext &funtc, JSFunctionBox *funbox, JSFunction *fun,
                            JSParseNode **list);
     JSParseNode *functionBody();
     JSParseNode *functionDef(JSAtom *name, FunctionType type, uintN lambda);
 
     JSParseNode *condition();
--- a/js/src/jsscan.cpp
+++ b/js/src/jsscan.cpp
@@ -1187,16 +1187,17 @@ TokenStream::getTokenInternal()
 
                             c = peekChar();
                             /* Strict mode code allows only \0, then a non-digit. */
                             if (val != 0 || JS7_ISDEC(c)) {
                                 if (!ReportStrictModeError(cx, this, NULL, NULL,
                                                            JSMSG_DEPRECATED_OCTAL)) {
                                     goto error;
                                 }
+                                setOctalCharacterEscape();
                             }
                             if ('0' <= c && c < '8') {
                                 val = 8 * val + JS7_UNDEC(c);
                                 getChar();
                                 c = peekChar();
                                 if ('0' <= c && c < '8') {
                                     int32 save = val;
                                     val = 8 * val + JS7_UNDEC(c);
--- a/js/src/jsscan.h
+++ b/js/src/jsscan.h
@@ -258,16 +258,17 @@ enum TokenStreamFlags
     TSF_UNEXPECTED_EOF = 0x10,  /* unexpected end of input, i.e. TOK_EOF not at top-level. */
     TSF_KEYWORD_IS_NAME = 0x20, /* Ignore keywords and return TOK_NAME instead to the parser. */
     TSF_STRICT_MODE_CODE = 0x40,/* Tokenize as appropriate for strict mode code. */
     TSF_DIRTYLINE = 0x80,       /* non-whitespace since start of line */
     TSF_OWNFILENAME = 0x100,    /* ts->filename is malloc'd */
     TSF_XMLTAGMODE = 0x200,     /* scanning within an XML tag in E4X */
     TSF_XMLTEXTMODE = 0x400,    /* scanning XMLText terminal from E4X */
     TSF_XMLONLYMODE = 0x800,    /* don't scan {expr} within text/tag */
+    TSF_OCTAL_CHAR = 0x1000,    /* observed a octal character escape */
 
     /*
      * To handle the hard case of contiguous HTML comments, we want to clear the
      * TSF_DIRTYINPUT flag at the end of each such comment.  But we'd rather not
      * scan for --> within every //-style comment unless we have to.  So we set
      * TSF_IN_HTML_COMMENT when a <!-- is scanned as an HTML begin-comment, and
      * clear it (and TSF_DIRTYINPUT) when we scan --> either on a clean line, or
      * only if (ts->flags & TSF_IN_HTML_COMMENT), in a //-style comment.
@@ -328,22 +329,25 @@ class TokenStream
     const char *getFilename() const { return filename; }
     uintN getLineno() const { return lineno; }
 
     /* Flag methods. */
     void setStrictMode(bool enabled = true) { setFlag(enabled, TSF_STRICT_MODE_CODE); }
     void setXMLTagMode(bool enabled = true) { setFlag(enabled, TSF_XMLTAGMODE); }
     void setXMLOnlyMode(bool enabled = true) { setFlag(enabled, TSF_XMLONLYMODE); }
     void setUnexpectedEOF(bool enabled = true) { setFlag(enabled, TSF_UNEXPECTED_EOF); }
+    void setOctalCharacterEscape(bool enabled = true) { setFlag(enabled, TSF_OCTAL_CHAR); }
+
     bool isStrictMode() { return !!(flags & TSF_STRICT_MODE_CODE); }
     bool isXMLTagMode() { return !!(flags & TSF_XMLTAGMODE); }
     bool isXMLOnlyMode() { return !!(flags & TSF_XMLONLYMODE); }
     bool isUnexpectedEOF() { return !!(flags & TSF_UNEXPECTED_EOF); }
     bool isEOF() const { return !!(flags & TSF_EOF); }
     bool isError() const { return !!(flags & TSF_ERROR); }
+    bool hasOctalCharacterEscape() const { return flags & TSF_OCTAL_CHAR; }
 
     /* Mutators. */
     bool reportCompileErrorNumberVA(JSParseNode *pn, uintN flags, uintN errorNumber, va_list ap);
     void mungeCurrentToken(TokenKind newKind) { tokens[cursor].type = newKind; }
     void mungeCurrentToken(JSOp newOp) { tokens[cursor].t_op = newOp; }
     void mungeCurrentToken(TokenKind newKind, JSOp newOp) {
         mungeCurrentToken(newKind);
         mungeCurrentToken(newOp);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_5/strict/directive-prologue-01.js
@@ -0,0 +1,78 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 601262;
+var summary =
+  "A string literal containing an octal escape before a strict mode " +
+  "directive should be a syntax error";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+try
+{
+  eval(" '\\145'; 'use strict'; ");
+  throw new Error("no error thrown for eval");
+}
+catch (e)
+{
+  assertEq(e instanceof SyntaxError, true,
+           "wrong error for octal-escape before strict directive in eval");
+}
+
+try
+{
+  Function(" '\\145'; 'use strict'; ");
+  throw new Error("no error thrown for Function");
+}
+catch (e)
+{
+  assertEq(e instanceof SyntaxError, true,
+           "wrong error for octal-escape before strict directive in Function");
+}
+
+try
+{
+  eval(" function f(){ '\\145'; 'use strict'; } ");
+  throw new Error("no error thrown for eval of function");
+}
+catch (e)
+{
+  assertEq(e instanceof SyntaxError, true,
+           "wrong error for octal-escape before strict directive in eval of " +
+           "function");
+}
+
+try
+{
+  Function(" function f(){ '\\145'; 'use strict'; } ");
+  throw new Error("no error thrown for eval of function");
+}
+catch (e)
+{
+  assertEq(e instanceof SyntaxError, true,
+           "wrong error for octal-escape before strict directive in eval of " +
+           "function");
+}
+
+eval("function notAnError1() { 5; '\\145'; function g() { 'use strict'; } }");
+
+Function("function notAnError2() { 5; '\\145'; function g() { 'use strict'; } }");
+
+function notAnError3()
+{
+  5;
+  "\145";
+  function g() { "use strict"; }
+}
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("All tests passed!");
--- a/js/src/tests/ecma_5/strict/jstests.list
+++ b/js/src/tests/ecma_5/strict/jstests.list
@@ -32,8 +32,9 @@ script 15.10.7.js
 script B.1.1.js
 script B.1.2.js
 script function-name-arity.js
 script primitive-this-no-writeback.js
 script regress-532254.js
 script regress-532041.js
 script unbrand-this.js
 script this-for-function-expression-recursion.js
+script directive-prologue-01.js