Bug 278878: Send preference parsing errors to browser console. r?bsmedberg draft
authorMilan Sreckovic <milan@mozilla.com>
Fri, 22 Apr 2016 15:35:01 -0400
changeset 355507 be3a06e85c65c8eddb3c496816e48f16295d0d95
parent 354827 1152d99d8c53ac9dae371a6e6d9fab03d3f98697
child 519217 9918c3891e122592d6015f1b7aba29d353cf0259
push id16311
push usermsreckovic@mozilla.com
push dateFri, 22 Apr 2016 19:35:13 +0000
reviewersbsmedberg
bugs278878
milestone48.0a1
Bug 278878: Send preference parsing errors to browser console. r?bsmedberg MozReview-Commit-ID: 61mi71dZbO8
modules/libpref/Preferences.cpp
modules/libpref/nsPrefBranch.cpp
modules/libpref/nsPrefBranch.h
modules/libpref/prefread.cpp
modules/libpref/prefread.h
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -214,16 +214,24 @@ AssertNotAlreadyCached(const char* aPref
       "Attempt to add a %s pref cache for preference '%s' at address '%p'"
       "was made. However, a pref was already cached at this address.\n",
       aPrefType, aPref, aPtr);
     MOZ_ASSERT(false, "Should not have an existing pref cache for this address");
   }
 }
 #endif
 
+static void
+ReportToConsole(const char* aMessage, int aLine, bool aError)
+{
+  nsPrintfCString message("** Preference parsing %s (line %d) = %s **\n",
+                          (aError ? "error" : "warning"), aLine, aMessage);
+  nsPrefBranch::ReportToConsole(NS_ConvertUTF8toUTF16(message.get()));
+}
+
 // Although this is a member of Preferences, it measures sPreferences and
 // several other global structures.
 /* static */ int64_t
 Preferences::SizeOfIncludingThisAndOtherStuff(mozilla::MallocSizeOf aMallocSizeOf)
 {
   NS_ENSURE_TRUE(InitStaticMembers(), 0);
 
   size_t n = aMallocSizeOf(sPreferences);
@@ -678,17 +686,17 @@ ReadExtensionPrefs(nsIFile *aFile)
     nsCOMPtr<nsIInputStream> stream;
     rv = reader->GetInputStream(entry, getter_AddRefs(stream));
     NS_ENSURE_SUCCESS(rv, rv);
 
     uint64_t avail;
     uint32_t read;
 
     PrefParseState ps;
-    PREF_InitParseState(&ps, PREF_ReaderCallback, nullptr);
+    PREF_InitParseState(&ps, PREF_ReaderCallback, ReportToConsole, nullptr);
     while (NS_SUCCEEDED(rv = stream->Available(&avail)) && avail) {
       rv = stream->Read(buffer, 4096, &read);
       if (NS_FAILED(rv)) {
         NS_WARNING("Pref stream read failed");
         break;
       }
 
       PREF_ParseBuf(&ps, buffer, read);
@@ -992,17 +1000,17 @@ static nsresult openPrefFile(nsIFile* aF
   NS_ENSURE_TRUE(fileSize64 <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
 
   uint32_t fileSize = (uint32_t)fileSize64;
   auto fileBuffer = MakeUniqueFallible<char[]>(fileSize);
   if (fileBuffer == nullptr)
     return NS_ERROR_OUT_OF_MEMORY;
 
   PrefParseState ps;
-  PREF_InitParseState(&ps, PREF_ReaderCallback, nullptr);
+  PREF_InitParseState(&ps, PREF_ReaderCallback, ReportToConsole, nullptr);
 
   // Read is not guaranteed to return a buf the size of fileSize,
   // but usually will.
   nsresult rv2 = NS_OK;
   uint32_t offset = 0;
   for (;;) {
     uint32_t amtRead = 0;
     rv = inStr->Read(fileBuffer.get(), fileSize, &amtRead);
@@ -1174,17 +1182,17 @@ static nsresult pref_LoadPrefsInDirList(
 }
 
 static nsresult pref_ReadPrefFromJar(nsZipArchive* jarReader, const char *name)
 {
   nsZipItemPtr<char> manifest(jarReader, name, true);
   NS_ENSURE_TRUE(manifest.Buffer(), NS_ERROR_NOT_AVAILABLE);
 
   PrefParseState ps;
-  PREF_InitParseState(&ps, PREF_ReaderCallback, nullptr);
+  PREF_InitParseState(&ps, PREF_ReaderCallback, ReportToConsole, nullptr);
   PREF_ParseBuf(&ps, manifest, manifest.Length());
   PREF_FinalizeParseState(&ps);
 
   return NS_OK;
 }
 
 //----------------------------------------------------------------------------------------
 // Initialize default preference JavaScript buffers from
--- a/modules/libpref/nsPrefBranch.cpp
+++ b/modules/libpref/nsPrefBranch.cpp
@@ -385,16 +385,27 @@ nsresult nsPrefBranch::CheckSanityOfStri
                                         getPrefName(aPrefName)));
   rv = console->LogStringMessage(NS_ConvertUTF8toUTF16(message).get());
   if (NS_FAILED(rv)) {
     return rv;
   }
   return NS_OK;
 }
 
+/*static*/
+void nsPrefBranch::ReportToConsole(const nsAString& aMessage)
+{
+  nsresult rv;
+  nsCOMPtr<nsIConsoleService> console = do_GetService("@mozilla.org/consoleservice;1", &rv);
+  if (NS_FAILED(rv)) {
+    return;
+  }
+  nsAutoString message(aMessage);
+  console->LogStringMessage(message.get());
+}
 
 NS_IMETHODIMP nsPrefBranch::SetComplexValue(const char *aPrefName, const nsIID & aType, nsISupports *aValue)
 {
   ENSURE_MAIN_PROCESS("Cannot SetComplexValue from content process:", aPrefName);
   NS_ENSURE_ARG(aPrefName);
 
   nsresult   rv = NS_NOINTERFACE;
 
--- a/modules/libpref/nsPrefBranch.h
+++ b/modules/libpref/nsPrefBranch.h
@@ -191,16 +191,18 @@ public:
   int32_t GetRootLength() { return mPrefRootLength; }
 
   nsresult RemoveObserverFromMap(const char *aDomain, nsISupports *aObserver);
 
   static void NotifyObserver(const char *newpref, void *data);
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
+  static void ReportToConsole(const nsAString& aMessage);
+
 protected:
   virtual ~nsPrefBranch();
 
   nsPrefBranch()    /* disallow use of this constructer */
     : mPrefRootLength(0)
     , mIsDefault(false)
     , mFreeingObserverList(false)
   {}
--- a/modules/libpref/prefread.cpp
+++ b/modules/libpref/prefread.cpp
@@ -93,16 +93,30 @@ pref_GrowBuf(PrefParseState *ps)
     ps->lbcur = ps->lb + curPos;
     ps->lbend = ps->lb + bufLen;
     ps->vb    = ps->lb + valPos;
 
     return true;
 }
 
 /**
+ * Report an error or a warning.  If not specified, just dump to stderr.
+ */
+static void
+pref_ReportParseProblem(PrefParseState& ps, const char* aMessage, int aLine, bool aError)
+{
+    if (ps.reporter) {
+        ps.reporter(aMessage, aLine, aError);
+    } else {
+        printf_stderr("**** Preference parsing %s (line %d) = %s **\n",
+                      (aError ? "error" : "warning"), aLine, aMessage);
+    }
+}
+
+/**
  * pref_DoCallback
  *
  * this function is called when a complete pref name-value pair has
  * been extracted from the input data.
  *
  * @param ps
  *        parse state instance
  *
@@ -114,16 +128,17 @@ pref_DoCallback(PrefParseState *ps)
     PrefValue  value;
 
     switch (ps->vtype) {
     case PrefType::String:
         value.stringVal = ps->vb;
         break;
     case PrefType::Int:
         if ((ps->vb[0] == '-' || ps->vb[0] == '+') && ps->vb[1] == '\0') {
+            pref_ReportParseProblem(*ps, "invalid integer value", 0, true);
             NS_WARNING("malformed integer value");
             return false;
         }
         value.intVal = atoi(ps->vb);
         break;
     case PrefType::Bool:
         value.boolVal = (ps->vb == kTrue);
         break;
@@ -131,21 +146,23 @@ pref_DoCallback(PrefParseState *ps)
         break;
     }
     (*ps->reader)(ps->closure, ps->lb, value, ps->vtype, ps->fdefault,
                   ps->fstickydefault);
     return true;
 }
 
 void
-PREF_InitParseState(PrefParseState *ps, PrefReader reader, void *closure)
+PREF_InitParseState(PrefParseState *ps, PrefReader reader,
+                    PrefParseErrorReporter reporter, void *closure)
 {
     memset(ps, 0, sizeof(*ps));
     ps->reader = reader;
     ps->closure = closure;
+    ps->reporter = reporter;
 }
 
 void
 PREF_FinalizeParseState(PrefParseState *ps)
 {
     if (ps->lb)
         free(ps->lb);
 }
@@ -174,19 +191,26 @@ PREF_FinalizeParseState(PrefParseState *
 bool
 PREF_ParseBuf(PrefParseState *ps, const char *buf, int bufLen)
 {
     const char *end;
     char c;
     char udigit;
     int state;
 
+    // The line number is currently only used for the error/warning reporting.
+    int lineNum = 0;
+
     state = ps->state;
     for (end = buf + bufLen; buf != end; ++buf) {
         c = *buf;
+        if (c == '\r' || c == '\n' || c == 0x1A) {
+            lineNum ++;
+        }
+
         switch (state) {
         /* initial state */
         case PREF_PARSE_INIT:
             if (ps->lbcur != ps->lb) { /* reset state */
                 ps->lbcur = ps->lb;
                 ps->vb    = nullptr;
                 ps->vtype = PrefType::Invalid;
                 ps->fdefault = false;
@@ -223,16 +247,17 @@ PREF_ParseBuf(PrefParseState *ps, const 
                 /* if we've matched all characters, then move to next state. */
                 if (ps->smatch[ps->sindex] == '\0') {
                     state = ps->nextstate;
                     ps->nextstate = PREF_PARSE_INIT; /* reset next state */
                 }
                 /* else wait for next char */
             }
             else {
+                pref_ReportParseProblem(*ps, "non-matching string", lineNum, true);
                 NS_WARNING("malformed pref file");
                 return false;
             }
             break;
 
         /* quoted string parsing */
         case PREF_PARSE_QUOTED_STRING:
             /* we assume that the initial quote has already been consumed */
@@ -258,32 +283,34 @@ PREF_ParseBuf(PrefParseState *ps, const 
                 ps->nextstate = PREF_PARSE_UNTIL_COMMA; /* return here when done */
                 state = PREF_PARSE_QUOTED_STRING;
             }
             else if (c == '/') {       /* allow embedded comment */
                 ps->nextstate = state; /* return here when done with comment */
                 state = PREF_PARSE_COMMENT_MAYBE_START;
             }
             else if (!isspace(c)) {
+                pref_ReportParseProblem(*ps, "need space, comment or quote", lineNum, true);
                 NS_WARNING("malformed pref file");
                 return false;
             }
             break;
 
         /* parse until we find a comma separating name and value */
         case PREF_PARSE_UNTIL_COMMA:
             if (c == ',') {
                 ps->vb = ps->lbcur;
                 state = PREF_PARSE_UNTIL_VALUE;
             }
             else if (c == '/') {       /* allow embedded comment */
                 ps->nextstate = state; /* return here when done with comment */
                 state = PREF_PARSE_COMMENT_MAYBE_START;
             }
             else if (!isspace(c)) {
+                pref_ReportParseProblem(*ps, "need space, comment or comma", lineNum, true);
                 NS_WARNING("malformed pref file");
                 return false;
             }
             break;
 
         /* value parsing */
         case PREF_PARSE_UNTIL_VALUE:
             /* the pref value type is unknown.  so, we scan for the first
@@ -310,16 +337,17 @@ PREF_ParseBuf(PrefParseState *ps, const 
                 *ps->lbcur++ = c;
                 state = PREF_PARSE_INT_VALUE;
             }
             else if (c == '/') {       /* allow embedded comment */
                 ps->nextstate = state; /* return here when done with comment */
                 state = PREF_PARSE_COMMENT_MAYBE_START;
             }
             else if (!isspace(c)) {
+                pref_ReportParseProblem(*ps, "need value, comment or space", lineNum, true);
                 NS_WARNING("malformed pref file");
                 return false;
             }
             break;
         case PREF_PARSE_INT_VALUE:
             /* grow line buffer if necessary... */
             if (ps->lbcur == ps->lbend && !pref_GrowBuf(ps))
                 return false; /* out of memory */
@@ -331,16 +359,17 @@ PREF_ParseBuf(PrefParseState *ps, const 
                     state = PREF_PARSE_UNTIL_SEMICOLON;
                 else if (c == '/') { /* allow embedded comment */
                     ps->nextstate = PREF_PARSE_UNTIL_CLOSE_PAREN;
                     state = PREF_PARSE_COMMENT_MAYBE_START;
                 }
                 else if (isspace(c))
                     state = PREF_PARSE_UNTIL_CLOSE_PAREN;
                 else {
+                    pref_ReportParseProblem(*ps, "while parsing integer", lineNum, true);
                     NS_WARNING("malformed pref file");
                     return false;
                 }
             }
             break;
 
         /* comment parsing */
         case PREF_PARSE_COMMENT_MAYBE_START:
@@ -348,22 +377,23 @@ PREF_ParseBuf(PrefParseState *ps, const 
             case '*': /* comment block */
                 state = PREF_PARSE_COMMENT_BLOCK;
                 break;
             case '/': /* comment line */
                 state = PREF_PARSE_UNTIL_EOL;
                 break;
             default:
                 /* pref file is malformed */
+                pref_ReportParseProblem(*ps, "while parsing comment", lineNum, true);
                 NS_WARNING("malformed pref file");
                 return false;
             }
             break;
         case PREF_PARSE_COMMENT_BLOCK:
-            if (c == '*') 
+            if (c == '*')
                 state = PREF_PARSE_COMMENT_BLOCK_MAYBE_END;
             break;
         case PREF_PARSE_COMMENT_BLOCK_MAYBE_END:
             switch (c) {
             case '/':
                 state = ps->nextstate;
                 ps->nextstate = PREF_PARSE_INIT;
                 break;
@@ -396,16 +426,18 @@ PREF_ParseBuf(PrefParseState *ps, const 
                 ps->esclen = 1;
                 ps->utf16[0] = ps->utf16[1] = 0;
                 ps->sindex = (c == 'x' ) ?
                                 HEX_ESC_NUM_DIGITS :
                                 UTF16_ESC_NUM_DIGITS;
                 state = PREF_PARSE_HEX_ESCAPE;
                 continue;
             default:
+                pref_ReportParseProblem(*ps, "preserving unexpected JS escape sequence",
+                                        lineNum, false);
                 NS_WARNING("preserving unexpected JS escape sequence");
                 /* Invalid escape sequence so we do have to write more than
                  * one character. Grow line buffer if necessary... */
                 if ((ps->lbcur+1) == ps->lbend && !pref_GrowBuf(ps))
                     return false; /* out of memory */
                 *ps->lbcur++ = '\\'; /* preserve the escape sequence */
                 break;
             }
@@ -418,16 +450,18 @@ PREF_ParseBuf(PrefParseState *ps, const 
             if ( c >= '0' && c <= '9' )
                 udigit = (c - '0');
             else if ( c >= 'A' && c <= 'F' )
                 udigit = (c - 'A') + 10;
             else if ( c >= 'a' && c <= 'f' )
                 udigit = (c - 'a') + 10;
             else {
                 /* bad escape sequence found, write out broken escape as-is */
+                pref_ReportParseProblem(*ps, "preserving invalid or incomplete hex escape",
+                                        lineNum, false);
                 NS_WARNING("preserving invalid or incomplete hex escape");
                 *ps->lbcur++ = '\\';  /* original escape slash */
                 if ((ps->lbcur + ps->esclen) >= ps->lbend && !pref_GrowBuf(ps))
                     return false;
                 for (int i = 0; i < ps->esclen; ++i)
                     *ps->lbcur++ = ps->esctmp[i];
 
                 /* push the non-hex character back for re-parsing. */
@@ -505,28 +539,32 @@ PREF_ParseBuf(PrefParseState *ps, const 
             /* tolerate only whitespace and embedded comments */
             if (c == '(')
                 state = PREF_PARSE_UNTIL_NAME;
             else if (c == '/') {
                 ps->nextstate = state; /* return here when done with comment */
                 state = PREF_PARSE_COMMENT_MAYBE_START;
             }
             else if (!isspace(c)) {
+                pref_ReportParseProblem(*ps, "need space, comment or open parentheses",
+                                        lineNum, true);
                 NS_WARNING("malformed pref file");
                 return false;
             }
             break;
         case PREF_PARSE_UNTIL_CLOSE_PAREN:
             /* tolerate only whitespace and embedded comments  */
             if (c == ')') {
                 state = PREF_PARSE_UNTIL_SEMICOLON;
             } else if (c == '/') {
                 ps->nextstate = state; /* return here when done with comment */
                 state = PREF_PARSE_COMMENT_MAYBE_START;
             } else if (!isspace(c)) {
+                pref_ReportParseProblem(*ps, "need space, comment or closing parentheses",
+                                        lineNum, true);
                 NS_WARNING("malformed pref file");
                 return false;
             }
             break;
 
         /* function terminator ';' parsing */
         case PREF_PARSE_UNTIL_SEMICOLON:
             /* tolerate only whitespace and embedded comments */
@@ -535,16 +573,18 @@ PREF_ParseBuf(PrefParseState *ps, const 
                     return false;
                 state = PREF_PARSE_INIT;
             }
             else if (c == '/') {
                 ps->nextstate = state; /* return here when done with comment */
                 state = PREF_PARSE_COMMENT_MAYBE_START;
             }
             else if (!isspace(c)) {
+                pref_ReportParseProblem(*ps, "need space, comment or semicolon",
+                                        lineNum, true);
                 NS_WARNING("malformed pref file");
                 return false;
             }
             break;
 
         /* eol parsing */
         case PREF_PARSE_UNTIL_EOL:
             /* need to handle mac, unix, or dos line endings.
@@ -598,17 +638,17 @@ main(int argc, char **argv)
     }
 
     fp = fopen(argv[1], "r");
     if (!fp) {
         printf("failed to open file\n");
         return -1;
     }
 
-    PREF_InitParseState(&ps, pref_reader, nullptr);
+    PREF_InitParseState(&ps, pref_reader, nullptr, nullptr);
 
     while ((n = fread(buf, 1, sizeof(buf), fp)) > 0)
         PREF_ParseBuf(&ps, buf, n);
 
     PREF_FinalizeParseState(&ps);
 
     fclose(fp);
     return 0;
--- a/modules/libpref/prefread.h
+++ b/modules/libpref/prefread.h
@@ -31,19 +31,25 @@ extern "C" {
  */
 typedef void (*PrefReader)(void       *closure,
                            const char *pref,
                            PrefValue   val,
                            PrefType    type,
                            bool        defPref,
                            bool        stickyPref);
 
+/**
+ * Report any errors or warnings we encounter during parsing.
+ */
+typedef void (*PrefParseErrorReporter)(const char* message, int line, bool error);
+
 /* structure fields are private */
 typedef struct PrefParseState {
     PrefReader  reader;
+    PrefParseErrorReporter reporter;
     void       *closure;
     int         state;      /* PREF_PARSE_...                */
     int         nextstate;  /* sometimes used...             */
     const char *smatch;     /* string to match               */
     int         sindex;     /* next char of smatch to check  */
                             /* also, counter in \u parsing   */
     char16_t   utf16[2];   /* parsing UTF16  (\u) escape    */
     int         esclen;     /* length in esctmp              */
@@ -57,26 +63,30 @@ typedef struct PrefParseState {
     bool        fdefault;   /* true if (default) pref        */
     bool        fstickydefault; /* true if (sticky) pref     */
 } PrefParseState;
 
 /**
  * PREF_InitParseState
  *
  * Called to initialize a PrefParseState instance.
- * 
+ *
  * @param ps
  *        PrefParseState instance.
  * @param reader
  *        PrefReader callback function, which will be called once for each
  *        preference name value pair extracted.
+ * @param reporter
+ *        PrefParseErrorReporter callback function, which will be called if we
+ *        encounter any errors (stop) or warnings (continue) during parsing.
  * @param closure
  *        PrefReader closure.
  */
-void PREF_InitParseState(PrefParseState *ps, PrefReader reader, void *closure);
+void PREF_InitParseState(PrefParseState *ps, PrefReader reader,
+			 PrefParseErrorReporter reporter, void *closure);
 
 /**
  * PREF_FinalizeParseState
  *
  * Called to release any memory in use by the PrefParseState instance.
  *
  * @param ps
  *        PrefParseState instance.