Closed Bug 636224 Opened 9 years ago Closed 9 years ago

rm cx->interpLevel

Categories

(Core :: JavaScript Engine, defect)

defect
Not set

Tracking

()

RESOLVED FIXED

People

(Reporter: luke, Assigned: luke)

References

Details

(Whiteboard: fixed-in-tracemonkey)

Attachments

(1 file, 1 obsolete file)

For a rainy day:

cx->interpLevel has only one use (suppressing errors in the scanner) and we have to inc/dec it for every RunScript call, giving its own stack guard etc.  Not the hottest path, but less state/code is better and cx->currentSegment ==/!= NULL should suffice.
Wow, ancient code. ISTM the scanner could just test JS_IsRunning instead.

/be
It's used to "Suppress any compile-time errors that don't occur at the top level."
Why do we do that?
(In reply to comment #2)
> It's used to "Suppress any compile-time errors that don't occur at the top
> level."
> Why do we do that?

The engine is old, parts pre-date exceptions (ES3). Originally errors were reported eagerly and scripts fail-stopped at first report.

  eval("oops=42; eval('oops + `')")

would report the bad character (`) and fail inside the inner eval; failure then propagated inevitably.

With exceptions (catchable via try-catch), the eager reporting had to be deferred by wrapping the report up as an exception:

  eval("oops=42; try{eval('oops + `')}catch(e){}")

In general, any intervening interpreter activation could have an exception handler active (not only via try-catch, either -- the JS API allows recovery from inner failure by native code, no JS compiler or try-catch syntax needed).

Errors-as-exceptions was the phrase used to describe the implicit conversion of seemingly (and theretofore) eager error reports into pending exceptions. Fine folks who did this work are long gone, and I wasn't in on all the code review, so I'm reconstructing partly from memory, partly from reading modern code.

Errors-as-exceptions meant that only if an uncaught exception propagated with failure return codes up to the outermost API entry point (see LAST_FRAME_CHECKS in jsapi.cpp) would the pending exception be turned back into a report, to avoid silent failure with lost exception, thus keeping API compatibility where it still made sense to automate error reporting via the JS_SetERrorReporter-configured callback.

Reports via such a callback go to stderr or an error console, and cannot be recalled.

So this interpLevel check dates from the errors-as-exceptions conversion days, but I don't believe it is necessary. The code already does convert errors to exceptions:

    /*
     * Try to raise an exception only if there isn't one already set --
     * otherwise the exception will describe the last compile-time error,
     * which is likely spurious.
     */
    if (!(flags & TSF_ERROR)) {
        if (js_ErrorToException(cx, message, &report, NULL, NULL))
            onError = NULL;
    }

    /*
     * Suppress any compile-time errors that don't occur at the top level.
     * This may still fail, as interplevel may be zero in contexts where we
     * don't really want to call the error reporter, as when js is called
     * by other code which could catch the error.
     */
    if (cx->interpLevel != 0 && !JSREPORT_IS_WARNING(flags))
        onError = NULL;

    if (onError) {
        JSDebugErrorHook hook = cx->debugHooks->debugErrorHook;

        /*
         * If debugErrorHook is present then we give it a chance to veto
         * sending the error on to the regular error reporter.
         */
        if (hook && !hook(cx, message, &report,
                          cx->debugHooks->debugErrorHookData)) {
            onError = NULL;
        }
    }
    if (onError)
        (*onError)(cx, message, &report);

The comment for the interpLevel test alludes to failure-to-suppress (presumably due to multiple JSContexts), so this code was never completely correct. But the previos paragraph, which calls js_ErrorToException, should have deferred the report unless TSF_ERROR were set.

A bit further, on the way out we have [*]:

    if (!JSREPORT_IS_WARNING(flags)) {
        /* Set the error flag to suppress spurious reports. */
        flags |= TSF_ERROR;
    }

And the only other place that sets TSF_ERROR is getTokenInternal on a lexical grammar error (such as that ` char). But that TSF_ERROR set must have followed a report, which called js_ErrorToException, e.g.:

      default:
        ReportCompileErrorNumber(cx, this, NULL, JSREPORT_ERROR, JSMSG_ILLEGAL_CHARACTER);
        goto error;

from getTokenInternal a bit above the out/eol_out/error labeled tail code. And this ReportCompileErrorNumber call invokes reportCompileErrorNumberVA which sets TSF_ERROR (for non-warnings), so there is no need for getTokenInternal to set it again.

So the interpLevel mess in reportCompileErrorNumberVA seems to be worried about multiple reports of non-warnings on the same ts and cx. But that implies a failure to propagate failure (fail-stop in pre-ES3 days; pending exception for syntax errors with ES3-era try/catch and after).

That is, this interpLevel and TSF_ERROR code is covering up spurious error reporting bugs. (We've never done error recovery in our compiler).

This says we should rip out interpLevel completely and set TSF_ERROR in exactly one place: reportCompileErrorNumberVA for non-warnings (as shown above at [*]; this code is fine), and assert that TSF_ERROR is not set before calling js_ErrorToException from reportCompileErrorNumberVA, and then test hard.

/be
Hah, get this: TokenStream::reportCompileErrorNumberVA has a formal arg 'flags' which shadows TokenStream::flags.  Thus, the (flags & TSF_ERROR) is bogus and flags |= TSF_ERROR isn't actually setting a bit.

I was looking at uses of TSF_ERROR and there only seems to be one:

      after_args:
        if (state == BAD_FORMAL && !ts.isError()) {

in jsfun.cpp in the Function constructor.  I don't really understand this stuff, but seems like maybe we can kill TSF_ERROR too?
(In reply to comment #4)
> Hah, get this: TokenStream::reportCompileErrorNumberVA has a formal arg 'flags'
> which shadows TokenStream::flags. 

Oh geez, we really need -Wshadow.
(In reply to comment #4)
> Hah, get this: TokenStream::reportCompileErrorNumberVA has a formal arg 'flags'
> which shadows TokenStream::flags.  Thus, the (flags & TSF_ERROR) is bogus and
> flags |= TSF_ERROR isn't actually setting a bit.

FWIW, the formal arg 'flags' is meant to be composed from these values:

/*            
 * JSErrorReport flag values.  These may be freely composed.
 */           
#define JSREPORT_ERROR      0x0     /* pseudo-flag for default case */
#define JSREPORT_WARNING    0x1     /* reported via JS_ReportWarning */
#define JSREPORT_EXCEPTION  0x2     /* exception was thrown */
#define JSREPORT_STRICT     0x4     /* error or warning due to strict option */
Attached patch kill kill (obsolete) — Splinter Review
This patch kills a goodly amount of code involving interpLevel and TOK_ERROR and tidies up Function().  It also plugs yet another one of these "holding a pointer into the chars of an unrooted string" holes.
Attachment #521313 - Flags: review?(nnethercote)
Passes trace/ref tests, try'ing now.
Oops, ignore the change to manifest.py, that was a temp hack so jstests.py works.
Comment on attachment 521313 [details] [diff] [review]
kill kill

> static JSBool
> Function(JSContext *cx, uintN argc, Value *vp)
> {
>-    JSObject *obj = NewFunction(cx, NULL);
>-    if (!obj)
>-        return JS_FALSE;
>-
>-    /* N.B. overwriting callee with return value */
>-    JSObject *parent = vp[0].toObject().getParent();
>-    vp[0].setObject(*obj);
>+    JS::Anchor<JSObject *> obj(NewFunction(cx, NULL));
>+    if (!obj.get())
>+        return false;

Function() still returns a JSBool, so this should be JS_FALSE.

>     if (!fun)
>-        return JS_FALSE;
>+        return false;

Likewise.  And a couple of other cases below.

The rest seems fine but a lot of these changes are over my head so I'll ask
brendan for a second opinion.
Attachment #521313 - Flags: review?(nnethercote)
Attachment #521313 - Flags: review?(brendan)
Attachment #521313 - Flags: review+
Oh, I see this:

cc1plus: warnings being treated as errors
../jsfun.cpp: In function ‘JSBool Function(JSContext*, uintN, js::Value*)’:
../jsfun.cpp:2623: error: ‘strAnchor.JS::Anchor<JSString*>::hold’ may be used uninitialized in this function

That'll need to be fixed.  Looks like a (strange) false positive;  the calls to Anchor::get() are dominated by calls to Anchor::set().  In fact, there's already this comment:

        strAnchor.set(cx->runtime->emptyString);  /* set to avoid warning */

Weird.


(In reply to comment #9)
> Oops, ignore the change to manifest.py, that was a temp hack so jstests.py
> works.

I too have that hack.  Any idea what the real problem is?
(In reply to comment #10)
> Function() still returns a JSBool, so this should be JS_FALSE.

This is normal SM style when the return type itself can't be changed to 'bool' b/c of signature restrictions.  The conversion is well-defined (so no correctness concern) and there is no perf lost.
(In reply to comment #11)
> Oh, I see this:
> 
> cc1plus: warnings being treated as errors
> ../jsfun.cpp: In function ‘JSBool Function(JSContext*, uintN, js::Value*)’:
> ../jsfun.cpp:2623: error: ‘strAnchor.JS::Anchor<JSString*>::hold’ may be used
> uninitialized in this function

Thanks, I forgot to compile in opt.

> I too have that hack.  Any idea what the real problem is?

No... I was under the impression someone was on it... maybe we are all under that impression...
(In reply to comment #4)
> I was looking at uses of TSF_ERROR and there only seems to be one:
> 
>       after_args:
>         if (state == BAD_FORMAL && !ts.isError()) {
> 
> in jsfun.cpp in the Function constructor.  I don't really understand this
> stuff, but seems like maybe we can kill TSF_ERROR too?

No, this is trying to avoid a double report. Second one would be spurious.

There are similar cases elsewhere that test for TOK_ERROR to avoid re-reporting.

/be

/be
(In reply to comment #14)
> > in jsfun.cpp in the Function constructor.  I don't really understand this
> > stuff, but seems like maybe we can kill TSF_ERROR too?
> 
> No, this is trying to avoid a double report. Second one would be spurious.
> 
> There are similar cases elsewhere that test for TOK_ERROR to avoid
> re-reporting.

Indeed, the patch uses the return token to avoid said double-reporting, thereby allowing TSF_ERROR to be removed.
Attachment #521313 - Attachment is obsolete: true
Attachment #521313 - Flags: review?(brendan)
Attachment #521861 - Flags: review?(brendan)
Comment on attachment 521861 [details] [diff] [review]
njn's comments addressed, thinko fix

>+OnBadFormal(JSContext *cx, TokenKind token)

Nit: canonical name is tt not token. Dates from TokenType daze, tk not really an improvement. Consistency with tradition trumps consistency in forming acronyms and hacker contractions?

>+{
>+    if (token != TOK_EOF)
>+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_FORMAL);

As noted (the above code is from the "kill kill" patch I was commenting on), this will double-report if token is TOK_ERROR. Your latest patch, which I'm reviewing here, does fix the bug, but without a test. Should be able to write one, e.g.

  Function("a, `", "");

or something like that.

Looks great otherwise. I'm not your manifest.py reviewer, though. r=me with the above covered by a test, and jorendorff's or dmandelin's Python-fu.

/be
Attachment #521861 - Flags: review?(brendan) → review+
(In reply to comment #16)
> Looks great otherwise. I'm not your manifest.py reviewer, though. r=me with the
> above covered by a test, and jorendorff's or dmandelin's Python-fu.

Oh, hah, that's my temp workaround for bug 644697; won't be landing that :)
http://hg.mozilla.org/tracemonkey/rev/4629abbde2f7
Whiteboard: fixed-in-tracemonkey
http://hg.mozilla.org/mozilla-central/rev/4629abbde2f7
Status: ASSIGNED → RESOLVED
Closed: 9 years ago
Resolution: --- → FIXED
Depends on: 678457
You need to log in before you can comment on or make changes to this bug.