Bug 278878: Send preference parsing errors to browser console. r?bsmedberg
MozReview-Commit-ID: 61mi71dZbO8
--- 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.