/* * Copyright (C) 2005 Apple Computer, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "WebCoreScriptDebugger.h" #import "DeprecatedString.h" #import "KURL.h" #import "PlatformString.h" #import "WebCoreObjCExtras.h" #import "WebScriptObjectPrivate.h" #import #import using namespace KJS; using namespace WebCore; @interface WebCoreScriptDebugger (WebCoreScriptDebuggerInternal) - (WebCoreScriptCallFrame *)_enterFrame:(ExecState *)state; - (WebCoreScriptCallFrame *)_leaveFrame; @end @interface WebCoreScriptCallFrame (WebCoreScriptDebuggerInternal) - (WebCoreScriptCallFrame *)_initWithGlobalObject:(WebScriptObject *)globalObj caller:(WebCoreScriptCallFrame *)caller state:(ExecState *)state; - (void)_setWrapper:(id)wrapper; - (id)_convertValueToObjcValue:(JSValue *)value; @end // convert UString to NSString static NSString *toNSString(const UString &s) { if (s.isEmpty()) return nil; return [NSString stringWithCharacters:(const unichar *)s.data() length:s.size()]; } // convert UString to NSURL static NSURL *toNSURL(const UString &s) { if (s.isEmpty()) return nil; return KURL(DeprecatedString(s)).getNSURL(); } // C++ interface to KJS debugger callbacks class WebCoreScriptDebuggerImp : public KJS::Debugger { private: WebCoreScriptDebugger *_objc; // our ObjC half bool _nested; // true => this is a nested call WebCoreScriptCallFrame *_current; // top stack frame (copy of same field from ObjC side) public: // constructor WebCoreScriptDebuggerImp(WebCoreScriptDebugger *objc, Interpreter *interp) : _objc(objc) { _nested = true; _current = [_objc _enterFrame:interp->globalExec()]; attach(interp); [[_objc delegate] enteredFrame:_current sourceId:-1 line:-1]; _nested = false; } // callbacks - relay to delegate virtual bool sourceParsed(ExecState *state, int sid, const UString &url, const UString &source, int lineNumber, int errorLine, const UString &errorMsg) { if (!_nested) { _nested = true; [[_objc delegate] parsedSource:toNSString(source) fromURL:toNSURL(url) sourceId:sid startLine:lineNumber errorLine:errorLine errorMessage:toNSString(errorMsg)]; _nested = false; } return true; } virtual bool callEvent(ExecState *state, int sid, int lineno, JSObject *func, const List &args) { if (!_nested) { _nested = true; _current = [_objc _enterFrame:state]; [[_objc delegate] enteredFrame:_current sourceId:sid line:lineno]; _nested = false; } return true; } virtual bool atStatement(ExecState *state, int sid, int lineno, int lastLine) { if (!_nested) { _nested = true; [[_objc delegate] hitStatement:_current sourceId:sid line:lineno]; _nested = false; } return true; } virtual bool returnEvent(ExecState *state, int sid, int lineno, JSObject *func) { if (!_nested) { _nested = true; [[_objc delegate] leavingFrame:_current sourceId:sid line:lineno]; _current = [_objc _leaveFrame]; _nested = false; } return true; } virtual bool exception(ExecState *state, int sid, int lineno, JSValue *exception) { if (!_nested) { _nested = true; [[_objc delegate] exceptionRaised:_current sourceId:sid line:lineno]; _nested = false; } return true; } }; // WebCoreScriptDebugger // // This is the main (behind-the-scenes) debugger class in WebCore. // // The WebCoreScriptDebugger has two faces, one for Objective-C (this class), and another (WebCoreScriptDebuggerImp) // for C++. The ObjC side creates the C++ side, which does the real work of attaching to the interpreter and // forwarding the KJS debugger callbacks to the delegate. @implementation WebCoreScriptDebugger #ifndef BUILDING_ON_TIGER + (void)initialize { WebCoreObjCFinalizeOnMainThread(self); } #endif - (WebCoreScriptDebugger *)initWithDelegate:(id)delegate { if ((self = [super init])) { _delegate = delegate; _globalObj = [_delegate globalObject]; _debugger = new WebCoreScriptDebuggerImp(self, [_globalObj _rootObject]->interpreter()); } return self; } - (void)dealloc { [_current release]; delete _debugger; [super dealloc]; } - (void)finalize { delete _debugger; [super finalize]; } - (id)delegate { return _delegate; } @end @implementation WebCoreScriptDebugger (WebCoreScriptDebuggerInternal) - (WebCoreScriptCallFrame *)_enterFrame:(ExecState *)state; { WebCoreScriptCallFrame *callee = [[WebCoreScriptCallFrame alloc] _initWithGlobalObject:_globalObj caller:_current state:state]; [callee _setWrapper:[_delegate newWrapperForFrame:callee]]; return _current = callee; } - (WebCoreScriptCallFrame *)_leaveFrame; { WebCoreScriptCallFrame *caller = [[_current caller] retain]; [_current release]; return _current = caller; } @end // WebCoreScriptCallFrame // // One of these is created to represent each stack frame. Additionally, there is a "global" // frame to represent the outermost scope. This global frame is always the last frame in // the chain of callers. // // The delegate can assign a "wrapper" to each frame object so it can relay calls through its // own exported interface. This class is private to WebCore (and the delegate). @implementation WebCoreScriptCallFrame (WebCoreScriptDebuggerInternal) - (WebCoreScriptCallFrame *)_initWithGlobalObject:(WebScriptObject *)globalObj caller:(WebCoreScriptCallFrame *)caller state:(ExecState *)state { if ((self = [super init])) { _globalObj = globalObj; _caller = caller; // (already retained) _state = state; } return self; } - (void)_setWrapper:(id)wrapper { _wrapper = wrapper; // (already retained) } - (id)_convertValueToObjcValue:(JSValue *)value { if (!value) return nil; if (value == [_globalObj _imp]) return _globalObj; Bindings::RootObject* root1 = [_globalObj _originRootObject]; if (!root1) return nil; Bindings::RootObject* root2 = [_globalObj _rootObject]; if (!root2) return nil; return [WebScriptObject _convertValueToObjcValue:value originRootObject:root1 rootObject:root2]; } @end @implementation WebCoreScriptCallFrame - (void)dealloc { [_wrapper release]; [_caller release]; [super dealloc]; } - (id)wrapper { return _wrapper; } - (WebCoreScriptCallFrame *)caller { return _caller; } // Returns an array of scope objects (most local first). // The properties of each scope object are the variables for that scope. // Note that the last entry in the array will _always_ be the global object (windowScriptObject), // whose properties are the global variables. - (NSArray *)scopeChain { Context* context = _state->context(); if (!context) { // global frame return [NSArray arrayWithObject:_globalObj]; } ScopeChain chain = context->scopeChain(); NSMutableArray *scopes = [[NSMutableArray alloc] init]; while (!chain.isEmpty()) { [scopes addObject:[self _convertValueToObjcValue:chain.top()]]; chain.pop(); } NSArray *result = [NSArray arrayWithArray:scopes]; [scopes release]; return result; } // Returns the name of the function for this frame, if available. // Returns nil for anonymous functions and for the global frame. - (NSString *)functionName { Context* context = _state->context(); if (context) { FunctionImp *func = context->function(); if (func) { Identifier fn = func->functionName(); return toNSString(fn.ustring()); } } return nil; } // Returns the pending exception for this frame (nil if none). - (id)exception { if (!_state->hadException()) return nil; return [self _convertValueToObjcValue:_state->exception()]; } // Evaluate some JavaScript code in the context of this frame. // The code is evaluated as if by "eval", and the result is returned. // If there is an (uncaught) exception, it is returned as though _it_ were the result. // Calling this method on the global frame is not quite the same as calling the WebScriptObject // method of the same name, due to the treatment of exceptions. - (id)evaluateWebScript:(NSString *)script { JSLock lock; UString code = String(script); ExecState *state = _state; Interpreter *interp = state->dynamicInterpreter(); JSObject *globObj = interp->globalObject(); // find "eval" JSObject *eval = NULL; if (state->context()) { // "eval" won't work without context (i.e. at global scope) JSValue *v = globObj->get(state, "eval"); if (v->isObject() && static_cast(v)->implementsCall()) eval = static_cast(v); else // no "eval" - fallback operates on global exec state state = interp->globalExec(); } JSValue *savedException = state->exception(); state->clearException(); // evaluate JSValue *result; if (eval) { List args; args.append(jsString(code)); result = eval->call(state, NULL, args); } else // no "eval", or no context (i.e. global scope) - use global fallback result = interp->evaluate(UString(), 0, code.data(), code.size(), globObj).value(); if (state->hadException()) result = state->exception(); // (may be redundant depending on which eval path was used) state->setException(savedException); return [self _convertValueToObjcValue:result]; } @end