Closed Bug 55594 Opened 20 years ago Closed 14 years ago

SSJS Request Object problem

Categories

(Core :: JavaScript Engine, defect, P3, major)

Sun
Solaris
defect

Tracking

()

RESOLVED WONTFIX

People

(Reporter: naomin, Unassigned)

Details

Request object is not accessable from a user defined method in iWS4.0 (in
conjunction with JavaScript14) but works
fine in NES3.x (in conjunction with JavaScript12).  When trying to access the
request object this way, it returns
"JSError: Request has no properties".

Here is a test application that demonstrates the problem.  If you try running it
on NES3.x it works fine, on iWS4.x it doesn't work.

Download WebServer 4.x. Enable ServerSide JavaScript. Create an SSJS application
as follows :

initial.html 
  ------------ 
  <SERVER> 
  project.log = new logger; 
  </SERVER> 

  index.html 
  ---------- 
  <HTML> 
  <BODY> 
  <FORM NAME="test4" ACTION="test4.html" METHOD=POST> 
  <B>Attribute:</B> 
  <INPUT TYPE="TEXT" NAME="Attribute" VALUE="" SIZE=40 MAXLENGTH=40> 
  <BR> 
  <INPUT TYPE="SUBMIT" NAME="test" VALUE="Go Test"> 
  </FORM> 
  </BODY> 
  </HTML> 

  functions.js 
  ------------ 
  function writeit() { 
     write('IP Address of user is: ' + request.ip + '<BR><BR>\n'); 
  } 

  function logger() { 
    this.foo = 'abc'; 
    this.callit = writeit; 
  } 

  test4.html 
  ---------- 
  <HTML> 
  <HEAD> 
  <TITLE>Request Object Test</TITLE> 
  </HEAD> 
  <BODY> 
  <SERVER> 
  project.log.callit(); 
  write('Form attribute is: ' + request.Attribute+ '\n'); 
  </SERVER> 
  </BODY> 
  </HTML> 

  jsa.conf 
  ----- 
  test4 uri="/test4" object="/..../test4.web" home="index.html"
start="initial.html" client-mode=client-cookie maxdbconnect=1 


Run "test4" application. In debug mode, the following is printed :

Request for address: 
  test4.html 

  Creating request object: 
  Attribute = "Hello" 
  test = "Go Test" 
  ip = "192.18.176.73" 
  protocol = "HTTP/1.0" 
  method = "POST" 
  agent = "Mozilla/4.7 [en] 
  (X11; U; SunOS 5.6 
  sun4u)" 
  uri = "/test4/test4.html" 

  Creating client object: 

  Initial project object: 
  log = "[object Object]" 

  Initial server object: 
  hostname = 
  "striper.red.iplanet.com:8889" 

  host = 
  "striper.red.iplanet.com" 
  protocol = "http:" 
  port = "8889" 
  httpdlwVersion = "4.1 
  Solaris" 
  jsVersion = "4.1 Solaris" 

  Serving page... 

  Error in 
  JavaScript:JSError: 
  services: 
  request has no 
  properties, filename = 
  functions.js, lineno = 0 

  Final request object: 
  Attribute = "Hello" 
  test = "Go Test" 
  ip = "192.18.176.73" 
  protocol = "HTTP/1.0" 
  method = "POST" 
  agent = "Mozilla/4.7 [en] 
  (X11; U; SunOS 5.6 
  sun4u)" 
  uri = "/test4/test4.html" 

  Final client object: 

  Final project object: 
  log = "[object Object]" 

  Final server object: 
  hostname = 
  "striper.red.iplanet.com:8889" 

  host = 
  "striper.red.iplanet.com" 
  protocol = "http:" 
  port = "8889" 
  httpdlwVersion = "4.1 
  Solaris" 
  jsVersion = "4.1 Solaris" 


  -----------------------------------------------------------

  At application level ,  in Debug mode all information is displayed correctly
such as Attribute, ip , test ... The debugger shows all correct info. Nothing
can be found wrong at this level.

At JavaScript engine level, in the user-defined function's scope. There is
something I can not explain. lvalue popped from the stack is a negative number
(v = -2147483647). This value is passed to js_ValueToObject
and evaluated as NULL or VOID, therefore obj becomes NULL object which indicates
"not found property".  Actually, none of the properties can be found at the
function level.

WebServer group really needs cooperation from JS engine group whose in-depth
knowledge in the JS engine may find the cause of this problem.
Naomi, is this problem still happening? 

In order for js_ValueToObject() to return an object, the lvalue passed 
into it will be a pointer. So the decimal representation of lvalue above
could be negative, depending on whether its highest-order bit is set or not.

Would you be able to provide a call stack? That will help us track
the problem down. Thanks -
Philip,
I put a breakpoint in js_ValueToNonNullObject, got the following stack :

=>[1] js_ValueToNonNullObject(cx = 0xd3f568, v = -2147483647), line 2493 in
"jsobj.c"
  [2] js_Interpret(cx = 0xd3f568, result = 0xe9142c9c), line 2172 in
"jsinterp.c"
  [3] js_Invoke(cx = 0xd3f568, argc = 1U, constructing = 0), line 670 in
"jsinterp.c"
  [4] js_Interpret(cx = 0xd3f568, result = 0xe9143228), line 2216 in
"jsinterp.c"
  [5] js_Execute(cx = 0xd3f568, chain = 0xd45470, script = 0xb43ee8, fun =
(nil), down = (nil), debugging = 0, result = 0xe9143228), line 829 in
"jsinterp.c"
  [6] JS_ExecuteScript(cx = 0xd3f568, obj = 0xd45470, script = 0xb43ee8, rval =
0xe9143228), line 2527 in "jsapi.c"
  [7] NSR_Context::executeScript(this = 0xd3f538, script = 0xb43ee8, jglobal =
0xa4c3a8, rval = 0xe9143228), line 211 in "context.cpp"
  [8] NSR_AE_executeScript(p_ctxt = 0xd3f538, pof = 0xb37ee0, scriptInterface =
0xd3f0dc, jscript = 0xb43ee8, jglobal = 0xa4c3a8), line 738 in "appenv.cpp"
  [9] livewireService(pb = 0x1fe050, sn = 0xb372b4, rq = 0xb372ec), line 1603 in
"nsapi.cpp"
  [10] func_native_pool_wait_work(fn = 0xed7547b8 = &livewireService, poolID =
0, pb = 0x1fe050, sn = 0xb372b4, rq = 0xb372ec), line 549 in "func.cpp"
  [11] func_exec_str(f = 0x544f08, pb = 0x1fe050, sn = 0xb372b4, rq = 0xb372ec),
line 213 in "func.cpp"
  [12] INTobject_execute(inst = 0x1fd8f8, sn = 0xb372b4, rq = 0xb372ec), line
777 in "func.cpp"
  [13] _perform_service(sn = 0xb372b4, rq = 0xb372ec, obj = 0x1fd898), line 796
in "httpact.cpp"
  [14] INTservact_service(sn = 0xb372b4, rq = 0xb372ec), line 822 in
"httpact.cpp"
  [15] INTservact_handle_processed(sn = 0xb372b4, rq = 0xb372ec), line 1314 in
"httpact.cpp"
  [16] HttpRequest::UnacceleratedRespond(this = 0xb37200, pzRequestLine =
0xb709a8 "POST /test4/test4.html HTTP/1.0", pzHost = 0xb709e0 "striper:8889",
pzAbsPath = 0xb709c8 "/test4/test4.html"), line 1549 in "httprequest.cpp"
  [17] HttpRequest::HandleRequest(this = 0xb37200, buf = 0xb6e8b0), line 812 in
"httprequest.cpp"
  [18] DaemonSession::Respond(this = 0xb370c0), line 643 in "daemonsession.cpp"
  [19] DaemonSession::ThreadMain(this = 0xb370c0), line 576 in
"daemonsession.cpp"
  [20] CThreadMain(vDaemonSession = 0xb370c0), line 264 in "daemonsession.cpp"
dbx: warning: can't find file
"/u/wtc/release/v3.5.1-19991110/sol26/dbg/ns/nspr20/pr/src/pthreads/ptthread.c"
dbx: warning: see `help pathmap'
  [21] _pt_root(arg = 0xb4c630), at 0xef180614


I printed cx and its components:

(/tools/ns/workshop/bin/dbx) p *cx
*cx = {
    links             = {
        next = 0x9db8e8
        prev = 0xd089a8
    }
    interpLevel       = 2U
    version           = JSVERSION_DEFAULT
    jsop_eq           = '\022'
    jsop_ne           = '\023'
    runtime           = 0x9db7b8
    stackPool         = {
        first     = {
            next  = 0xd7cd38
            base  = 13890960U
            limit = 13890960U
            avail = 13890960U
        }
        current   = 0xd7cd38
        arenasize = 8192U
        mask      = 3U
    }
    fp                = 0xe9142cac
    codePool          = {
        first     = {
            next  = (nil)
            base  = 13890992U
            limit = 13890992U
            avail = 13890992U
        }
        current   = 0xd3f5a0
        arenasize = 1024U
        mask      = 0
    }
    tempPool          = {
        first     = {
            next  = (nil)
            base  = 13891024U
            limit = 13891024U
            avail = 13891024U
        }
        current   = 0xd3f5bc
        arenasize = 1024U
        mask      = 7U
    }
    globalObject      = 0xd45470
    newborn           = (0xd45508, 0xd454e8, (nil), (nil))
    regExpStatics     = {
        input        = (nil)
        multiline    = 0
        parenCount   = 0
        moreLength   = 0
        parens       = (
{
            length = 0
            chars  = (nil)
        }{
            length = 0
            chars  = (nil)
        }{
            length = 0
            chars  = (nil)
        }{
            length = 0
            chars  = (nil)
        }{
            length = 0
            chars  = (nil)
        }{
            length = 0
            chars  = (nil)
        }{
            length = 0
            chars  = (nil)
        }{
            length = 0
            chars  = (nil)
        }{
            length = 0
            chars  = (nil)
        }
)
        moreParens   = (nil)
        lastMatch    = {
            length = 0
            chars  = 0xed5f5d98
        }
        lastParen    = {
            length = 0
            chars  = 0xed5f5d98
        }
        leftContext  = {
            length = 0
            chars  = 0xed5f5d98
        }
        rightContext = {
            length = 0
            chars  = 0xed5f5d98
        }
    }
    sharpObjectMap    = {
        depth    = 0
        sharpgen = 0
        table    = (nil)
    }
    argumentFormatMap = (nil)
    lastMessage       = (nil)
    tracefp           = (nil)
    branchCallback    = 0xed75e258 = &ScriptJSBranchCallBack()
    errorReporter     = 0xed75ea28 = &ScriptJSErrorReporter()
    data              = (nil)
    dormantFrameChain = (nil)
    gcDisabled        = 0
    thread            = 11847216
    requestDepth      = 1
    gcActive          = '\0'
    throwing          = '\0'
    exception         = 0
}

(/tools/ns/workshop/bin/dbx) p *(JSClass *)(cx->fp->scopeChain->slots[2]-1) 
*(struct JSClass *) (cx->fp->scopeChain->slots[2]-1) = {
    name         = 0xed5f3330 "Function"
    flags        = 5U
    addProperty  = 0xed51f7b8 = &JS_PropertyStub(struct JSContext *cx, struct
JSObject *obj, jsval id, jsval *vp)
    delProperty  = 0xed5590f8 = &fun_delProperty()
    getProperty  = 0xed559220 = &fun_getProperty()
    setProperty  = 0xed559a28 = &fun_setProperty()
    enumerate    = 0xed5597c0 = &fun_enumProperty()
    resolve      = 0xed559d60 = &fun_resolve()
    convert      = 0xed55a1b0 = &fun_convert()
    finalize     = 0xed55a290 = &fun_finalize()
    getObjectOps = (nil)
    checkAccess  = (nil)
    call         = (nil)
    construct    = (nil)
    xdrObject    = 0xed55a3c8 = &fun_xdrObject()
    hasInstance  = 0xed55aea0 = &fun_hasInstance()
    spare        = (0, 0)
}

(/tools/ns/workshop/bin/dbx)p *(JSFunction*)(cx->fp->scopeChain->slots[3]-1) 
*(struct JSFunction *) (cx->fp->scopeChain->slots[3]-1) = {
    nrefs  = 2
    object = 0xa4c4f8
    call   = (nil)
    nargs  = 0
    extra  = 0
    nvars  = 0
    flags  = '\0'
    spare  = '\0'
    atom   = 0xb2c8b8
    script = 0xb246c8
    clasp  = (nil)
}

Note that chars in atom is displayed "writeit" which is the name of the
user-defined function in which the request is made.

(/tools/ns/workshop/bin/dbx)p
*(JSClass*)(((JSObject*)cx->fp->scopeChain->slots[1])->slots[2]-1) 

*(struct JSClass *) (((struct JSObject *)
cx->fp->scopeChain->slots[1])->slots[2]-1) = {
    name         = 0xed79920c "global"
    flags        = 0
    addProperty  = 0xed51f7b8 = &JS_PropertyStub(struct JSContext *cx, struct
JSObject *obj, jsval id, jsval *vp)
    delProperty  = 0xed51f7b8 = &JS_PropertyStub(struct JSContext *cx, struct
JSObject *obj, jsval id, jsval *vp)
    getProperty  = 0xed51f7b8 = &JS_PropertyStub(struct JSContext *cx, struct
JSObject *obj, jsval id, jsval *vp)
    setProperty  = 0xed51f7b8 = &JS_PropertyStub(struct JSContext *cx, struct
JSObject *obj, jsval id, jsval *vp)
    enumerate    = 0xed51f828 = &JS_EnumerateStub(struct JSContext *cx, struct
JSObject *obj)
    resolve      = 0xed771cb8 = &req_resolve(struct JSContext *cx, struct
JSObject *obj, long id)
    convert      = 0xed51f8f8 = &JS_ConvertStub(struct JSContext *cx, struct
JSObject *obj, JSType type, jsval *vp)
    finalize     = 0xed51f998 = &JS_FinalizeStub(struct JSContext *cx, struct
JSObject *obj)
    getObjectOps = (nil)
    checkAccess  = (nil)
    call         = (nil)
    construct    = (nil)
    xdrObject    = (nil)
    hasInstance  = (nil)
    spare        = (0, 0)
}

Let me know if you need more info.
cc'ing jband, mccabe. 

John, here is a stack trace of the bug we were discussing yesterday.
Can we see what's wrong from this? Why is it that the parameter "v" 
at the top of the stack has a decimal representation, when all the 
other parameters are in hex? 


Also note Naomi's comment,


"Note that chars in atom is displayed "writeit" which is the name of the
user-defined function in which the request is made."

-2147483647 is just the decimal representation of 0x80000001. This is the value 
of the constant JSVAL_VOID (see the top of jsapi.h). It looks like jsval is 
ultimately typedef'd to long and that particular debugger decides to print that 
type as signed decimal. This is not the problem. The problem is that request is 
just not found in the scope when this function is invoked.

naomin, Is there any other reason to think that this is a core JS engine 
problem? I can't see why this JS code *should* work. If you pass request into 
writeit/callit then it should work fine. But, I don't see how you could expect 
the request object to be in the static scope of the writeit function which is 
attached to the project object. Unless ssjs is doing something really odd, it 
seems to me, that this function is created in the scope of whatever global is 
current when initial.html is run. That selfsame global object doesn't get a 
'request' object mapped in as a property when you later receive a request does 
it?
js_ValueToNonNullObject(JSContext *cx, jsval v)
{
    JSObject *obj;
    JSString *str;

    if (!js_ValueToObject(cx, v, &obj))
	return NULL;
    if (!obj) {
	str = js_DecompileValueGenerator(cx, v, NULL);
	if (str) {
	    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
				 JSMSG_NO_PROPERTIES, JS_GetStringBytes(str));
	}
    }
    return obj;
}

v has the value of 0x80000001. It was passed in the call to js_ValueToObject :

JSBool
js_ValueToObject(JSContext *cx, jsval v, JSObject **objp)
{
    JSObject *obj;

    if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v)) {
	obj = NULL;
    } else if (JSVAL_IS_OBJECT(v)) {
	obj = JSVAL_TO_OBJECT(v);
	if (!OBJ_DEFAULT_VALUE(cx, obj, JSTYPE_OBJECT, &v))
	    return JS_FALSE;
	if (JSVAL_IS_OBJECT(v))
	    obj = JSVAL_TO_OBJECT(v);
    } else {
	if (JSVAL_IS_STRING(v)) {
	    obj = js_StringToObject(cx, JSVAL_TO_STRING(v));
	} else if (JSVAL_IS_INT(v)) {
	    obj = js_NumberToObject(cx, (jsdouble)JSVAL_TO_INT(v));
	} else if (JSVAL_IS_DOUBLE(v)) {
	    obj = js_NumberToObject(cx, *JSVAL_TO_DOUBLE(v));
	} else {
	    JS_ASSERT(JSVAL_IS_BOOLEAN(v));
	    obj = js_BooleanToObject(cx, JSVAL_TO_BOOLEAN(v));
	}
	if (!obj)
	    return JS_FALSE;
    }
    *objp = obj;
    return JS_TRUE;
}

objp is null and JS_TRUE is returned to js_ValueToNonNullObject. Since obj is
null, the following code is executed :

    if (!obj) {
	str = js_DecompileValueGenerator(cx, v, NULL);
	if (str) {
	    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
				 JSMSG_NO_PROPERTIES, JS_GetStringBytes(str));
	}
    }

js_DecompileValueGenerator returns a name "request". And then 
JS_ReportErrorNumber is called to report the error. 

Look like js_DecompileValueGenerator can't find the request. I don't know at
detail level how this function does it.

Note that Nescape Enterprise Server 3.6 works using the same test case. NES3.6
uses JavaScript 1.2 or some version earlier than JavaScript 1.4. I don't blame
core JS code completely. The problem may come from both sides. That is why I
need your expertise in JS code to analyze the cause.

A couple weeks ago, I provided a work-around to customers: request should be
passed to project.log.callit(request) and put request in the function parameter
of function writeit. 

The testcase has worked in NES3.6. What makes it fail on NES 4.x ? I have looked
at SSJS application layer and found nothing suspicous.
I asked Brendan about why ssjs could have ever succeeded with this kind of 
lookup that didn't follow the static scoping rules. He said that older versions 
of ssjs likely used the old (and removed) JSFUN_GLOBAL_PARENT flag:

#define JSFUN_GLOBAL_PARENT     0x80    /* reparent calls to cx->globalObject */

Assuming that this behavior is truly required, then whoever adapted ssjs to use 
a newer JS engine should have worked around this in some way that would emulate 
the old behavior for user scripts.

naomin, do you know if there are likely to be many real world scripts that will 
break? Were you heavily involved with the conversion of ssjs to use the newer 
engine? If not, is there anyone still around who you might ask about details of 
that conversion? Can you look at the code change history and see what kind of 
changes accompanied the (presumed) removal of the used of JSFUN_GLOBAL_PARENT?

Brendan had some suggestions for how one might do that. Perhaps he'll add them 
here...
Status: UNCONFIRMED → NEW
Ever confirmed: true
John,
I was not involved in the conversion of ssjs to accomodate the newer JS. I have
been with the company for less than a year! All the SSJS folks are gone. Looking
at the history of SSJS changes, vishy is the one that ported JS1.4 to NES4.0. I
will contact him about this issue. Hopefully, he still remembers what he did
almost two years ago. At the same time, if your group have any suggestion how to
deal with this, please let me know. Thanks !
JS1.4 supported JSFUN_GLOBAL_PARENT, IIRC.  Anyway that's not a supported engine
rev, AFAIK.  Reassigning to beard.

/be
Assignee: rogerl → beard
Sorry, what are IIRC and AFAIK ?
IIRC = If I Recall Correctly
AFAIK = As Far As I Know

Sorry about geeking out there!

/be
Why isn't the workaround "request should be passed to 
project.log.callit(request)" sufficient? If it isn't, then I'd guess you need to 
define the request object in a more global scope, so it is seen by the code 
called from test4.html.

What isn't clear to me, is the JavaScript code in the SERVER element wrapped in 
its own function, which is passed request as an, or is it evaluated at top-level? 
If it is evaluated at top-level, then request would seem to be a global variable 
already, and then would be visible to the writeit.

Dynamic scoping has fallen into disrepute over the years. Passing the request as 
an explicit parameter is a MUCH better idea, and leads to much more maintainable 
code.
beard: the JSFUN_GLOBAL_PARENT hack was a limited form of dynamic scoping that 
looked like static scoping in the SSJS environment.  It allowed functions to be 
compiled once, offline, and their bytecodes loaded and functions adopted by a 
"superglobal".  When called, these functions were reparented to the running cx's 
globalObject.  No one was the wiser, because different server threads cannot see 
one anothers request objects (which are the threads' contexts' globalObjects, if 
that makes sense).

I don't know who broke things in this case, which has nothing to do with any 
Mozilla code.  I think this bug shouldn't even be in bugzilla, but it's not the 
worst offense in that regard.  SSJS folks, please grep for JSFUN_GLOBAL_PARENT 
in all sources (SSJS and your JS1.4 snapshot).

/be
When a future SSJS implementation migrates to JS 1.5, a workaround for this will 
have to be developed. Changing milestone to Future.
Status: NEW → ASSIGNED
Target Milestone: --- → Future
Assignee: beard → general
Status: ASSIGNED → NEW
QA Contact: pschwartau → general
Target Milestone: Future → ---
Status: NEW → RESOLVED
Closed: 14 years ago
Resolution: --- → WONTFIX
You need to log in before you can comment on or make changes to this bug.