570 lines
17 KiB
Java
570 lines
17 KiB
Java
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is mozilla.org code.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Netscape Communications Corporation.
|
|
* Portions created by the Initial Developer are Copyright (C) 1998
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
/*
|
|
* 'Model' that manages breakpoints
|
|
*/
|
|
|
|
// when who what
|
|
// 06/27/97 jband added this header to my code
|
|
//
|
|
|
|
package com.netscape.jsdebugging.ifcui;
|
|
|
|
import java.util.Observable;
|
|
import java.util.Observer;
|
|
import netscape.application.*;
|
|
import netscape.util.*;
|
|
import com.netscape.jsdebugging.ifcui.palomar.util.*;
|
|
import com.netscape.jsdebugging.api.*;
|
|
import netscape.security.PrivilegeManager;
|
|
|
|
// scripts stored in a Vector with newest scripts at the high end
|
|
|
|
public class BreakpointTyrant
|
|
extends Observable
|
|
implements Observer, PrefsSupport
|
|
{
|
|
public BreakpointTyrant(Emperor emperor)
|
|
{
|
|
super();
|
|
_emperor = emperor;
|
|
_controlTyrant = emperor.getControlTyrant();
|
|
if(AS.S)ER.T(null!=_controlTyrant,"emperor init order problem", this);
|
|
|
|
PrivilegeManager.enablePrivilege("Debugger");
|
|
_dc = _emperor.getDebugController();
|
|
|
|
// set script hook
|
|
_scriptHook = new BPTyrantScriptHook(this);
|
|
_scriptHook.setNextHook(_dc.setScriptHook(_scriptHook));
|
|
|
|
_breakpoints = new Hashtable();
|
|
_scripts = new Vector();
|
|
|
|
_controlTyrant.addObserver(this);
|
|
|
|
if(AS.DEBUG)
|
|
{
|
|
_uiThreadForAssertCheck = Thread.currentThread();
|
|
}
|
|
|
|
_enabled = true;
|
|
// process the scripts currently known by the controller
|
|
_dc.iterateScripts(_scriptHook);
|
|
}
|
|
|
|
public Breakpoint findBreakpoint( Location loc )
|
|
{
|
|
return (Breakpoint) _breakpoints.get(loc);
|
|
}
|
|
|
|
public Breakpoint findBreakpoint( Breakpoint bp )
|
|
{
|
|
return (Breakpoint) _breakpoints.get(bp.getLocation());
|
|
}
|
|
|
|
|
|
public synchronized Breakpoint addBreakpoint( Location loc )
|
|
{
|
|
Breakpoint bp;
|
|
|
|
if( null != (bp = findBreakpoint(loc)) )
|
|
{
|
|
if(AS.S)ER.T(false,"attempted to add existing Breakpoint: "+bp,this);
|
|
return bp;
|
|
}
|
|
|
|
loc = _getBestBPLocationForLocation(loc,null);
|
|
bp = new Breakpoint(loc);
|
|
_breakpoints.put(loc,bp);
|
|
_notifyObservers( BreakpointTyrantUpdate.ADD_ONE, bp );
|
|
_setHooksForBP(bp);
|
|
|
|
return bp;
|
|
}
|
|
|
|
|
|
public synchronized void removeBreakpoint( Breakpoint bp )
|
|
{
|
|
if( null == findBreakpoint(bp) )
|
|
{
|
|
if(AS.S)ER.T(false,"attempted to remove non-existant Breakpoint: "+bp,this);
|
|
return;
|
|
}
|
|
|
|
_clearHooksForBP(bp);
|
|
_breakpoints.remove(bp.getLocation());
|
|
_notifyObservers( BreakpointTyrantUpdate.REMOVE_ONE, bp );
|
|
}
|
|
|
|
public synchronized void removeAllBreakpoints()
|
|
{
|
|
Enumeration e = _breakpoints.elements();
|
|
|
|
while( e.hasMoreElements() )
|
|
{
|
|
Breakpoint bp = (Breakpoint) e.nextElement();
|
|
_clearHooksForBP(bp);
|
|
}
|
|
|
|
_breakpoints.clear();
|
|
_notifyObservers( BreakpointTyrantUpdate.REMOVE_ALL, null );
|
|
}
|
|
|
|
public synchronized void removeAllBreakpointsForURL( String url )
|
|
{
|
|
|
|
Vector vec = getBreakpointsForURL(url);
|
|
int count = vec.count();
|
|
if( 0 == count )
|
|
return;
|
|
|
|
for( int i = 0; i < count; i++ )
|
|
{
|
|
Breakpoint bp = (Breakpoint) vec.elementAt(i);
|
|
_clearHooksForBP(bp);
|
|
_breakpoints.remove(bp.getLocation());
|
|
}
|
|
_notifyObservers( BreakpointTyrantUpdate.REMOVE_ALL_FOR_URL, new Breakpoint(url,0) );
|
|
}
|
|
|
|
public Vector getBreakpointsForURL( String url )
|
|
{
|
|
Vector vret = new Vector();
|
|
|
|
Enumeration e = _breakpoints.elements();
|
|
while( e.hasMoreElements() )
|
|
{
|
|
Breakpoint bp = (Breakpoint) e.nextElement();
|
|
if( url.equals(bp.getURL()) )
|
|
vret.addElement(bp);
|
|
}
|
|
return vret;
|
|
}
|
|
|
|
|
|
public Object[] getAllBreakpoints()
|
|
{
|
|
return _breakpoints.elementsArray();
|
|
}
|
|
|
|
public Object[] getAllScripts()
|
|
{
|
|
int count = _scripts.count();
|
|
if( 0 == count )
|
|
return null;
|
|
Object[] a = new Object[count];
|
|
|
|
// we reverse the list so that newest scripts are first
|
|
|
|
for( int i = count-1; i >= 0; i-- )
|
|
a[count-(i+1)] = _scripts.elementAt(i);
|
|
return a;
|
|
}
|
|
|
|
public Vector getScriptsVectorForURL(String url)
|
|
{
|
|
Vector vret = new Vector();
|
|
|
|
// we reverse the list so that newest scripts are first
|
|
|
|
int count = _scripts.count();
|
|
for( int i = count-1; i >= 0; i-- )
|
|
{
|
|
Script script = (Script) _scripts.elementAt(i);
|
|
if( url.equals(script.getURL()) )
|
|
vret.addElement(script);
|
|
}
|
|
return vret;
|
|
}
|
|
|
|
public Object[] getScriptsForURL(String url)
|
|
{
|
|
return getScriptsVectorForURL(url).elementArray();
|
|
}
|
|
|
|
public void modifiedBreakpoint( Breakpoint bp )
|
|
{
|
|
_notifyObservers(BreakpointTyrantUpdate.MODIFIED_ONE, bp);
|
|
}
|
|
|
|
/*******************************/
|
|
|
|
private boolean _isNonShadowedLocationInScript(Location loc, Script script)
|
|
{
|
|
if( ! _isLocationInScript(loc, script) )
|
|
return false;
|
|
|
|
if( null != script.getFunction() )
|
|
return true;
|
|
|
|
boolean oursSeen = false;
|
|
int count = _scripts.count();
|
|
// we reverse the list so that newest scripts are first
|
|
for( int i = count-1; i >= 0; i-- )
|
|
{
|
|
Script scriptCur = (Script) _scripts.elementAt(i);
|
|
if( script.equals(scriptCur) )
|
|
{
|
|
oursSeen = true;
|
|
continue;
|
|
}
|
|
if( ! oursSeen )
|
|
continue;
|
|
|
|
if( _isLocationInScript(loc, scriptCur) )
|
|
{
|
|
if( null != scriptCur.getFunction() )
|
|
return false;
|
|
return true;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private boolean _isLocationInScript(Location loc, Script script)
|
|
{
|
|
if( ! loc.getURL().equals(script.getURL()) )
|
|
return false;
|
|
|
|
int line = loc.getLine();
|
|
ScriptSection[] sections = script.getSections();
|
|
for( int i = 0; i < sections.length; i++ )
|
|
{
|
|
int base = sections[i].getBaseLineNumber();
|
|
int extent = sections[i].getLineExtent();
|
|
if( line >= base && line < base+extent )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private Script _getNewestScriptForLocation( Location loc )
|
|
{
|
|
Script scriptTopLevel = null;
|
|
Script scriptBest = null;
|
|
|
|
int count = _scripts.count();
|
|
for( int i = count-1; i >= 0; i-- )
|
|
{
|
|
Script script = (Script) _scripts.elementAt(i);
|
|
if( _isLocationInScript(loc, script) )
|
|
{
|
|
if( null != script.getFunction() )
|
|
scriptBest = script;
|
|
else if( null != scriptTopLevel )
|
|
break;
|
|
else
|
|
scriptBest = scriptTopLevel = script;
|
|
}
|
|
}
|
|
return scriptBest;
|
|
}
|
|
|
|
// 'script' can be null
|
|
private Location _getBestBPLocationForLocation(Location loc, Script script)
|
|
{
|
|
if( null == script )
|
|
script = _getNewestScriptForLocation(loc);
|
|
|
|
if( null == script )
|
|
return loc;
|
|
|
|
JSPC pc = script.getClosestPC(loc.getLine());
|
|
if( null == pc )
|
|
return loc;
|
|
|
|
JSSourceLocation sloc = (JSSourceLocation) pc.getSourceLocation();
|
|
if( null == sloc )
|
|
return loc;
|
|
|
|
if( sloc.getLine() == loc.getLine() )
|
|
return loc;
|
|
|
|
return new Location( loc.getURL(), sloc.getLine() );
|
|
}
|
|
|
|
private synchronized void _setHooksForBP(Breakpoint bp)
|
|
{
|
|
PrivilegeManager.enablePrivilege("Debugger");
|
|
Location loc = bp.getLocation();
|
|
boolean setAny = false;
|
|
|
|
int count = _scripts.count();
|
|
for( int i = 0; i < count; i++ )
|
|
{
|
|
Script script = (Script) _scripts.elementAt(i);
|
|
if( ! _isNonShadowedLocationInScript(loc, script) )
|
|
continue;
|
|
|
|
JSPC pc = script.getClosestPC(bp.getLine());
|
|
// if(AS.S)ER.T(null==pc,"null returned from script.getClosestPC",this);
|
|
if( null == pc )
|
|
continue;
|
|
|
|
// generate a hook (doublelinked to BP)
|
|
BreakpointHook hook = new BreakpointHook(_emperor,pc,_controlTyrant,bp);
|
|
bp.putHook(script, hook);
|
|
|
|
// set hook (with chaining)
|
|
hook.setNextHook( _dc.setInstructionHook(hook.getPC(),hook) );
|
|
setAny = true;
|
|
}
|
|
if( setAny )
|
|
_notifyObservers(BreakpointTyrantUpdate.ACTIVATED_ONE, bp);
|
|
}
|
|
|
|
private synchronized void _clearHooksForBP(Breakpoint bp)
|
|
{
|
|
boolean clearedAny = false;
|
|
|
|
Hashtable hookTable = bp.getHooks();
|
|
Enumeration e = hookTable.elements();
|
|
while( e.hasMoreElements() )
|
|
{
|
|
BreakpointHook hook = (BreakpointHook) e.nextElement();
|
|
|
|
// somewhat dubious chaining...
|
|
PrivilegeManager.enablePrivilege("Debugger");
|
|
_dc.setInstructionHook(hook.getPC(), hook.getNextHook());
|
|
hook.setTyrant(null);
|
|
hook.setBreakpoint(null);
|
|
clearedAny = true;
|
|
}
|
|
hookTable.clear();
|
|
|
|
if( clearedAny )
|
|
_notifyObservers(BreakpointTyrantUpdate.DEACTIVATED_ONE, bp);
|
|
}
|
|
|
|
private Vector _getBreakpointsForScript(Script script)
|
|
{
|
|
Vector vret = new Vector();
|
|
|
|
Enumeration e = _breakpoints.elements();
|
|
while( e.hasMoreElements() )
|
|
{
|
|
Breakpoint bp = (Breakpoint) e.nextElement();
|
|
if( _isNonShadowedLocationInScript(bp.getLocation(), script) )
|
|
vret.addElement(bp);
|
|
}
|
|
return vret;
|
|
}
|
|
|
|
private synchronized void _setHooksForScript(Script script)
|
|
{
|
|
PrivilegeManager.enablePrivilege("Debugger");
|
|
Vector vec = _getBreakpointsForScript(script);
|
|
|
|
int count = vec.count();
|
|
if( 0 == count )
|
|
return;
|
|
|
|
for( int i = 0; i < count; i++ )
|
|
{
|
|
Breakpoint bp = (Breakpoint) vec.elementAt(i);
|
|
|
|
JSPC pc = script.getClosestPC(bp.getLine());
|
|
// if(AS.S)ER.T(null==pc,"null returned from script.getClosestPC",this);
|
|
if( null == pc )
|
|
continue;
|
|
|
|
// generate a hook (doublelinked to BP)
|
|
BreakpointHook hook = new BreakpointHook(_emperor,pc,_controlTyrant,bp);
|
|
bp.putHook(script, hook);
|
|
// set hook (with chaining)
|
|
hook.setNextHook( _dc.setInstructionHook(hook.getPC(),hook) );
|
|
|
|
// this is called on native thread, can not do notify!!!
|
|
// _notifyObservers(BreakpointTyrantUpdate.ACTIVATED_ONE, bp);
|
|
}
|
|
}
|
|
|
|
private synchronized void _clearHooksForScript(Script script)
|
|
{
|
|
Vector vec = _getBreakpointsForScript(script);
|
|
|
|
int count = vec.count();
|
|
if( 0 == count )
|
|
return;
|
|
|
|
for( int i = 0; i < count; i++ )
|
|
{
|
|
Breakpoint bp = (Breakpoint) vec.elementAt(i);
|
|
BreakpointHook hook = (BreakpointHook) bp.getHook(script);
|
|
if( null == hook )
|
|
continue;
|
|
// somewhat dubious chaining...
|
|
PrivilegeManager.enablePrivilege("Debugger");
|
|
_dc.setInstructionHook(hook.getPC(), hook.getNextHook());
|
|
|
|
hook.setTyrant(null);
|
|
hook.setBreakpoint(null);
|
|
bp.removeHook(script);
|
|
|
|
// this is called on native thread, can not do notify!!!
|
|
// _notifyObservers(BreakpointTyrantUpdate.DEACTIVATED_ONE, bp);
|
|
}
|
|
}
|
|
|
|
// implement observer interface
|
|
public void update(Observable o, Object arg)
|
|
{
|
|
if( o == _controlTyrant )
|
|
{
|
|
int type = ((ControlTyrantUpdate)arg).type;
|
|
if( ControlTyrantUpdate.DEBUGGER_DISABLED == type )
|
|
{
|
|
_enabled = false;
|
|
removeAllBreakpoints();
|
|
}
|
|
}
|
|
}
|
|
|
|
// helper
|
|
private void _notifyObservers( int type, Breakpoint bp )
|
|
{
|
|
if(AS.S)ER.T(Thread.currentThread()==_uiThreadForAssertCheck,"_notifyObservers called on thread other than UI thread",this);
|
|
setChanged();
|
|
notifyObservers( new BreakpointTyrantUpdate(type,bp) );
|
|
}
|
|
|
|
// these are called by our script hook
|
|
public void justLoadedScript(Script script)
|
|
{
|
|
// if(AS.DEBUG)System.out.println( "loaded script: " + script );
|
|
if( ! _enabled )
|
|
return;
|
|
if( ! script.isValid() )
|
|
return;
|
|
|
|
synchronized(this)
|
|
{
|
|
// add to the table of scripts
|
|
_scripts.addElement(script);
|
|
_setHooksForScript(script);
|
|
}
|
|
}
|
|
public void aboutToUnloadScript(Script script)
|
|
{
|
|
// if(AS.DEBUG)System.out.println( "unloaded script: " + script );
|
|
if( ! _enabled )
|
|
return;
|
|
synchronized(this)
|
|
{
|
|
_scripts.removeElement(script);
|
|
_clearHooksForScript(script);
|
|
}
|
|
}
|
|
|
|
// implement PrefsSupport interface
|
|
public void prefsWrite(Archiver archiver) throws CodingException
|
|
{
|
|
BreakpointSaver bs = new BreakpointSaver();
|
|
bs.getFromTyrant(this);
|
|
archiver.archiveRootObject(bs);
|
|
}
|
|
public void prefsRead(Unarchiver unarchiver) throws CodingException
|
|
{
|
|
int id = Util.RootIdentifierForClassName(unarchiver.archive(),
|
|
"com.netscape.jsdebugging.ifcui.BreakpointSaver" );
|
|
if( -1 != id )
|
|
{
|
|
BreakpointSaver bs = (BreakpointSaver)
|
|
unarchiver.unarchiveIdentifier(id);
|
|
bs.sendToTyrant(this);
|
|
}
|
|
}
|
|
|
|
|
|
// data...
|
|
|
|
private Emperor _emperor;
|
|
private ControlTyrant _controlTyrant;
|
|
private DebugController _dc;
|
|
|
|
private Hashtable _breakpoints;
|
|
private Vector _scripts;
|
|
|
|
private BPTyrantScriptHook _scriptHook;
|
|
private boolean _enabled = false;
|
|
private Thread _uiThreadForAssertCheck = null;
|
|
}
|
|
|
|
// used here only...
|
|
class BPTyrantScriptHook
|
|
extends ScriptHook
|
|
implements ChainableHook
|
|
{
|
|
public BPTyrantScriptHook(BreakpointTyrant bpTyrant)
|
|
{
|
|
setTyrant(bpTyrant);
|
|
}
|
|
|
|
public void justLoadedScript(Script script)
|
|
{
|
|
// for safety we make sure not to throw anything at native caller.
|
|
try {
|
|
if( null != _bpTyrant )
|
|
_bpTyrant.justLoadedScript(script);
|
|
if( null != _nextHook )
|
|
_nextHook.justLoadedScript(script);
|
|
} catch(Throwable t){} // eat it.
|
|
}
|
|
|
|
public void aboutToUnloadScript(Script script)
|
|
{
|
|
// for safety we make sure not to throw anything at native caller.
|
|
try {
|
|
if( null != _bpTyrant )
|
|
_bpTyrant.aboutToUnloadScript(script);
|
|
if( null != _nextHook )
|
|
_nextHook.aboutToUnloadScript(script);
|
|
} catch(Throwable t){} // eat it.
|
|
}
|
|
|
|
// implement ChainableHook
|
|
public void setTyrant(Object tyrant) {_bpTyrant = (BreakpointTyrant) tyrant;}
|
|
public void setNextHook(Hook nextHook) {_nextHook = (ScriptHook) nextHook;}
|
|
|
|
private BreakpointTyrant _bpTyrant;
|
|
private ScriptHook _nextHook;
|
|
|
|
}
|