Fix bug 49286 "try/catch within JavaScript not working as expected"

Also, accept patches from Igor:

Subject:
             Rhino: UintMap optimization
        Date:
             Fri, 06 Jul 2001 13:14:49 +0200
       From:
             Igor Bukanov <igor@icesoft.no>
 Organization:
             Wind River
         To:
             Norris Boyd <nboyd@atg.com>




Hi, Norris!

Currently omj.Node uses Hashtable to map int property types to
objects/integer. In my opinion this is very inefficient: to store single
int property it creates 5 objects: one for property Hahstable, 2 Integer
wrappers for property/value, array to sore Hahstable slots and Hashtable
slot itself. To fix this I added omj.UintMap class that can map
non-negative integers to objects or integers and modified omj.Node to
use it. The class is a hashtable implementation that uses one int[] and
one Object[] arrays to store keys/values and Object[] array is not
created if the map contains only integers.

To take full advantage of omj.UintMap code has to be modified to use
Node.getIntProp/Node.putIntProp to store int properties, but even in
this form it is a win.

I can provide patches to use Node.getIntProp/Node.putIntProp and UintMap
for InterpreterData.itsLineNumberTable if this is OK.

Regards, Igor
This commit is contained in:
nboyd%atg.com 2001-07-10 17:30:16 +00:00
parent 0cab42e99d
commit f7a0fa338c
9 changed files with 596 additions and 49 deletions

View File

@ -1590,6 +1590,14 @@ public class Context {
throw new RuntimeException("Bad feature index: " + featureIndex);
}
/**
* Get/Set threshold of executed instructions counter that triggers call to
* <code>observeInstructionCount()</code>.
* When the threshold is zero, instruction counting is disabled,
* otherwise each time the run-time executes at least the threshold value
* of script instructions, <code>observeInstructionCount()</code> will
* be called.
*/
public int getInstructionObserverThreshold() {
return instructionThreshold;
}
@ -1598,6 +1606,17 @@ public class Context {
instructionThreshold = threshold;
}
/**
* Allow application to monitor counter of executed script instructions
* in Context subclasses.
* Run-time calls this when instruction counting is enabled and the counter
* reaches limit set by <code>setInstructionObserverThreshold()</code>.
* The method is useful to observe long running scripts and if necessary
* to terminate them.
* @param instructionCount amount of script instruction executed since
* last call to <code>observeInstructionCount</code>
* @throws Error to terminate the script
*/
protected void observeInstructionCount(int instructionCount) {}
/********** end of API **********/

View File

@ -2075,14 +2075,12 @@ public class Interpreter extends LabelTable {
stack[++stackTop] = Undefined.instance;
break;
case TokenStream.THROW :
cx.interpreterSecurityDomain = null;
result = stack[stackTop];
if (result == DBL_MRK)
result = doubleWrap(sDbl[stackTop]);
--stackTop;
throw new JavaScriptException(result);
case TokenStream.JTHROW :
cx.interpreterSecurityDomain = null;
result = stack[stackTop];
// No need to check for DBL_MRK: result is Exception
--stackTop;
@ -2197,6 +2195,8 @@ public class Interpreter extends LabelTable {
int exceptionType;
Object errObj;
if (ex instanceof WrappedException)
ex = (Exception) ((WrappedException)ex).unwrap();
if (ex instanceof EcmaError) {
errObj = ((EcmaError)ex).getErrorObject();
exceptionType = ECMA;

View File

@ -102,7 +102,8 @@ class JavaMembers {
throw new RuntimeException("unexpected IllegalAccessException "+
"accessing Java field");
} catch (InvocationTargetException e) {
throw new WrappedException(e.getTargetException());
throw WrappedException.wrapException(
JavaScriptException.wrapException(scope, e));
}
// Need to wrap the object before we return it.
scope = ScriptableObject.getTopLevelScope(scope);
@ -183,8 +184,8 @@ class JavaMembers {
}
public void put(String name, Object javaObject, Object value,
boolean isStatic)
public void put(Scriptable scope, String name, Object javaObject,
Object value, boolean isStatic)
{
Hashtable ht = isStatic ? staticMembers : members;
Object member = ht.get(name);
@ -212,7 +213,8 @@ class JavaMembers {
throw new RuntimeException("unexpected IllegalAccessException " +
"accessing Java field");
} catch (InvocationTargetException e) {
throw new WrappedException(e.getTargetException());
throw WrappedException.wrapException(
JavaScriptException.wrapException(scope, e));
}
}
else {

View File

@ -111,7 +111,7 @@ public class NativeJavaClass extends NativeJavaObject implements Function {
}
public void put(String name, Scriptable start, Object value) {
members.put(name, javaObject, value, true);
members.put(this, name, javaObject, value, true);
}
public Object[] getIds() {

View File

@ -103,7 +103,7 @@ public class NativeJavaObject implements Scriptable, Wrapper {
// prototype. Since we can't add a property to a Java object,
// we modify it in the prototype rather than copy it down.
if (prototype == null || members.has(name, false))
members.put(name, javaObject, value, false);
members.put(this, name, javaObject, value, false);
else
prototype.put(name, prototype, value);
}

View File

@ -37,8 +37,6 @@
package org.mozilla.javascript;
import java.util.*;
/**
* This class implements the root of the intermediate representation.
*
@ -330,16 +328,32 @@ public class Node implements Cloneable {
public Object getProp(int propType) {
if (props == null)
return null;
return props.get(new Integer(propType));
return props.getObject(propType);
}
public int getIntProp(int propType, int defaultValue) {
if (props == null)
return defaultValue;
return props.getInt(propType, defaultValue);
}
public int getExistingIntProp(int propType) {
return props.getExistingInt(propType);
}
public void putProp(int propType, Object prop) {
if (props == null)
props = new Hashtable(2);
props = new UintMap(2);
if (prop == null)
props.remove(new Integer(propType));
props.remove(propType);
else
props.put(new Integer(propType), prop);
props.put(propType, prop);
}
public void putIntProp(int propType, int prop) {
if (props == null)
props = new UintMap(2);
props.put(propType, prop);
}
public Object getDatum() {
@ -394,15 +408,13 @@ public class Node implements Cloneable {
if (props == null)
return sb.toString();
Enumeration keys = props.keys();
Enumeration elems = props.elements();
while (keys.hasMoreElements()) {
Integer key = (Integer) keys.nextElement();
Object elem = elems.nextElement();
int[] keys = props.getKeys();
for (int i = 0; i != keys.length; ++i) {
int key = keys[i];
sb.append(" [");
sb.append(propToString(key.intValue()));
sb.append(propToString(key));
sb.append(": ");
switch (key.intValue()) {
switch (key) {
case FIXUPS_PROP : // can't add this as it recurses
sb.append("fixups property");
break;
@ -416,7 +428,12 @@ public class Node implements Cloneable {
sb.append("last use property");
break;
default :
sb.append(elem.toString());
if (props.isObjectType(key)) {
sb.append(props.getObject(key).toString());
}
else {
sb.append(props.getExistingInt(key));
}
break;
}
sb.append(']');
@ -463,7 +480,7 @@ public class Node implements Cloneable {
protected Node next; // next sibling
protected Node first; // first element of a linked list of children
protected Node last; // last element of a linked list of children
protected Hashtable props;
protected UintMap props;
protected Object datum; // encapsulated data; depends on type
}

View File

@ -676,6 +676,17 @@ public class ScriptRuntime {
return jse.value;
}
/**
* Check a WrappedException. Unwrap a JavaScriptException and return
* the value, otherwise rethrow.
*/
public static Object unwrapWrappedException(WrappedException we) {
Throwable t = we.getWrappedException();
if (t instanceof JavaScriptException)
return ((JavaScriptException) t).value;
throw we;
}
public static Object getProp(Object obj, String id, Scriptable scope) {
Scriptable start;
if (obj instanceof Scriptable) {

View File

@ -0,0 +1,468 @@
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* The contents of this file are subject to the Netscape 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/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1997-2000 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
* Igor Bukanov
*
* Alternatively, the contents of this file may be used under the
* terms of the GNU Public License (the "GPL"), in which case the
* provisions of the GPL are applicable instead of those above.
* If you wish to allow use of your version of this file only
* under the terms of the GPL and not to allow others to use your
* version of this file under the NPL, indicate your decision by
* deleting the provisions above and replace them with the notice
* and other provisions required by the GPL. If you do not delete
* the provisions above, a recipient may use your version of this
* file under either the NPL or the GPL.
*/
package org.mozilla.javascript;
/**
* Map to associate non-negative integers to objects or integers.
* The map does not synchronize any of its operation, so either use
* it from a single thread or do own synchronization or perform all mutation
* operations on one thread before passing the map to others
*
* @author Igor Bukanov
*
*/
class UintMap {
// Map implementation via hashtable,
// follows "The Art of Computer Programming" by Donald E. Knuth
public UintMap() {
this(4);
}
public UintMap(int initialCapacity) {
if (checkWorld) check(initialCapacity >= 0);
// Table grow when number of stored keys >= 3/4 of max capacity
int minimalCapacity = initialCapacity * 4 / 3;
int i;
for (i = 2; (1 << i) < minimalCapacity; ++i) { }
minimalPower = i;
if (checkSelf) check(minimalPower >= 2);
}
public boolean isEmpty() {
return keyCount == 0;
}
public int size() {
return keyCount;
}
public boolean has(int key) {
if (checkWorld) check(key >= 0);
return 0 <= findIndex(key);
}
public boolean isObjectType(int key) {
if (checkWorld) check(key >= 0);
int index = findIndex(key);
return index >= 0 && isObjectTypeValue(index);
}
public boolean isIntType(int key) {
if (checkWorld) check(key >= 0);
int index = findIndex(key);
return index >= 0 && !isObjectTypeValue(index);
}
/**
* Get object value assigned with key.
* @return key object value or null if key is absent or does
* not have object value
*/
public Object getObject(int key) {
if (checkWorld) check(key >= 0);
if (values != null) {
int index = findIndex(key);
if (0 <= index) {
return values[index];
}
}
return null;
}
/**
* Get integer value assigned with key.
* @return key integer value or defaultValue if key is absent or does
* not have int value
*/
public int getInt(int key, int defaultValue) {
if (checkWorld) check(key >= 0);
if (ivaluesShift != 0) {
int index = findIndex(key);
if (0 <= index) {
if (!isObjectTypeValue(index)) {
return keys[ivaluesShift + index];
}
}
}
return defaultValue;
}
/**
* Get integer value assigned with key.
* @return key integer value or defaultValue if key does not exist or does
* not have int value
* @throws RuntimeException if key does not exist or does
* not have int value
*/
public int getExistingInt(int key) {
if (checkWorld) check(key >= 0);
if (ivaluesShift != 0) {
int index = findIndex(key);
if (0 <= index) {
if (!isObjectTypeValue(index)) {
return keys[ivaluesShift + index];
}
}
}
// Key must exist
if (checkWorld) check(false);
return 0;
}
public void put(int key, Object value) {
if (checkWorld) check(key >= 0 && value != null);
int index = ensureIndex(key, false);
if (values == null) {
values = new Object[1 << power];
}
values[index] = value;
}
public void put(int key, int value) {
if (checkWorld) check(key >= 0);
int index = ensureIndex(key, true);
if (ivaluesShift == 0) {
int N = 1 << power;
int[] tmp = new int[N * 2];
System.arraycopy(keys, 0, tmp, 0, N);
keys = tmp;
ivaluesShift = N;
}
keys[ivaluesShift + index] = value;
if (values != null) { values[index] = null; }
}
public void remove(int key) {
if (checkWorld) check(key >= 0);
int index = findIndex(key);
if (0 <= index) {
keys[index] = DELETED;
--keyCount;
if (values != null) { values[index] = null; }
}
}
public void clear() {
power = 0;
keys = null;
values = null;
ivaluesShift = 0;
keyCount = 0;
occupiedCount = 0;
}
/** Return array of present keys */
public int[] getKeys() {
int[] keys = this.keys;
int n = keyCount;
int[] result = new int[n];
for (int i = 0; n != 0; ++i) {
int entry = keys[i];
if (entry != EMPTY && entry != DELETED) {
result[--n] = entry;
}
}
return result;
}
private static int tableLookupStep(int fraction, int mask, int power) {
int shift = 32 - 2 * power;
if (shift >= 0) {
return ((fraction >>> shift) & mask) | 1;
}
else {
return (fraction & (mask >>> -shift)) | 1;
}
}
private int findIndex(int key) {
int[] keys = this.keys;
if (keys != null) {
int fraction = key * A;
int index = fraction >>> (32 - power);
int entry = keys[index];
if (entry == key) { return index; }
if (entry != EMPTY) {
// Search in table after first failed attempt
int mask = (1 << power) - 1;
int step = tableLookupStep(fraction, mask, power);
int n = 0;
do {
if (checkSelf) check(n++ < occupiedCount);
index = (index + step) & mask;
entry = keys[index];
if (entry == key) { return index; }
} while (entry != EMPTY);
}
}
return -1;
}
private int getFreeIndex(int key) {
int[] keys = this.keys;
int fraction = key * A;
int index = fraction >>> (32 - power);
if (keys[index] != EMPTY) {
int mask = (1 << power) - 1;
int step = tableLookupStep(fraction, mask, power);
int firstIndex = index;
do {
if (checkSelf) check(keys[index] != DELETED);
index = (index + step) & mask;
if (checkSelf) check(firstIndex != index);
} while (keys[index] != EMPTY);
}
return index;
}
private void rehashTable(boolean ensureIntSpace) {
if (keys == null) { power = minimalPower; }
else {
// Check if removing deleted entries would free enough space
if (keyCount * 2 >= occupiedCount) {
// Need to grow: less then half of deleted entries
++power;
}
}
int N = 1 << power;
int[] old = keys;
int oldShift = ivaluesShift;
if (oldShift == 0 && !ensureIntSpace) {
keys = new int[N];
}
else {
ivaluesShift = N; keys = new int[N * 2];
}
for (int i = 0; i != N; ++i) { keys[i] = EMPTY; }
Object[] oldValues = values;
if (oldValues != null) { values = new Object[N]; }
if (old != null) {
for (int i = 0, remaining = keyCount; remaining != 0; ++i) {
int entry = old[i];
if (entry != EMPTY && entry != DELETED) {
int index = getFreeIndex(entry);
keys[index] = entry;
if (oldValues != null) {
values[index] = oldValues[i];
}
if (oldShift != 0) {
keys[ivaluesShift + index] = old[oldShift + i];
}
--remaining;
}
}
}
occupiedCount = keyCount;
}
// Ensure key index creating one if necessary
private int ensureIndex(int key, boolean intType) {
int index = -1;
int firstDeleted = -1;
int[] keys = this.keys;
if (keys != null) {
int fraction = key * A;
index = fraction >>> (32 - power);
int entry = keys[index];
if (entry == key) { return index; }
if (entry != EMPTY) {
if (entry == DELETED) { firstDeleted = index; }
// Search in table after first failed attempt
int mask = (1 << power) - 1;
int step = tableLookupStep(fraction, mask, power);
int n = 0;
do {
if (checkSelf) check(n++ < occupiedCount);
index = (index + step) & mask;
entry = keys[index];
if (entry == key) { return index; }
if (entry == DELETED && firstDeleted < 0) {
firstDeleted = index;
}
} while (entry != EMPTY);
}
}
// Inserting of new key
if (checkSelf) check(keys == null || keys[index] == EMPTY);
if (firstDeleted >= 0) {
index = firstDeleted;
}
else {
// Need to consume empty entry: check occupation level
if (keys == null || occupiedCount * 4 >= (1 << power) * 3) {
// Too litle unused entries: rehash
rehashTable(intType);
keys = this.keys;
index = getFreeIndex(key);
}
++occupiedCount;
}
keys[index] = key;
++keyCount;
return index;
}
private boolean isObjectTypeValue(int index) {
if (checkSelf) check(index >= 0 && index < (1 << power));
return values != null && values[index] != null;
}
private static void check(boolean condition) {
if (!condition) { throw new RuntimeException(); }
}
// Rudimentary support for Design-by-Contract
private static final boolean checkWorld = true;
private static final boolean checkSelf = checkWorld && false;
// A == golden_ratio * (1 << 32) = ((sqrt(5) - 1) / 2) * (1 << 32)
// See Knuth etc.
private static final int A = 0x9e3779b9;
private static final int EMPTY = -1;
private static final int DELETED = -2;
// Structure of kyes and values arrays (N == 1 << power):
// keys[0 <= i < N]: key value or EMPTY or DELETED mark
// values[0 <= i < N]: value of key at keys[i]
// keys[N <= i < 2N]: int values of keys at keys[i - N]
private int[] keys;
private Object[] values;
private int minimalPower;
private int power;
private int keyCount;
private int occupiedCount; // == keyCount + deleted_count
// If ivaluesShift != 0, keys[ivaluesShift + index] contains integer
// values associated with keys
private int ivaluesShift;
/*
public static void main(String[] args) {
UintMap map;
map = new UintMap();
testHash(map, 10 * 1000);
map = new UintMap(30 * 1000);
testHash(map, 10 * 100);
map.clear();
testHash(map, 4);
map = new UintMap(0);
testHash(map, 10 * 100);
}
private static void testHash(UintMap map, int N) {
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
map.put(i, i);
check(i == map.getInt(i, -1));
}
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
map.put(i, i);
check(i == map.getInt(i, -1));
}
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
map.put(i, new Integer(i));
check(-1 == map.getInt(i, -1));
Integer obj = (Integer)map.getObject(i);
check(obj != null && i == obj.intValue());
}
check(map.size() == N);
System.out.print("."); System.out.flush();
int[] keys = map.getKeys();
check(keys.length == N);
for (int i = 0; i != N; ++i) {
int key = keys[i];
check(map.has(key));
check(!map.isIntType(key));
check(map.isObjectType(key));
Integer obj = (Integer) map.getObject(key);
check(obj != null && key == obj.intValue());
}
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
check(-1 == map.getInt(i, -1));
}
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
map.put(i * i, i);
check(i == map.getInt(i * i, -1));
}
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
check(i == map.getInt(i * i, -1));
}
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
map.put(i * i, new Integer(i));
check(-1 == map.getInt(i * i, -1));
map.remove(i * i);
check(!map.has(i * i));
map.put(i * i, i);
check(map.isIntType(i * i));
check(null == map.getObject(i * i));
map.remove(i * i);
check(!map.isObjectType(i * i));
check(!map.isIntType(i * i));
}
int old_size = map.size();
for (int i = 0; i != N; ++i) {
map.remove(i * i);
check(map.size() == old_size);
}
System.out.println(); System.out.flush();
}
//*/
}

View File

@ -2184,44 +2184,29 @@ public class Codegen extends Interpreter {
int realEnd = acquireLabel();
addByteCode(ByteCode.GOTO, realEnd);
// javascript handler; unwrap exception and GOTO to javascript
// catch area.
if (catchTarget != null) {
int jsHandler = classFile.markHandler(acquireLabel());
// MS JVM gets cranky if the exception object is left on the stack
short exceptionObject = getNewWordLocal();
astore(exceptionObject);
// reset the variable object local
aload(savedVariableObject);
astore(variableObjectLocal);
aload(exceptionObject);
releaseWordLocal(exceptionObject);
// unwrap the exception...
addScriptRuntimeInvoke("unwrapJavaScriptException",
"(Lorg/mozilla/javascript/JavaScriptException;)",
"Ljava/lang/Object;");
// get the label to goto
int catchLabel =
((Integer)catchTarget.getProp(Node.LABEL_PROP)).intValue();
addByteCode(ByteCode.GOTO, catchLabel);
// mark the handler
classFile.addExceptionHandler
(startLabel, catchLabel, jsHandler,
"org/mozilla/javascript/JavaScriptException");
generateCatchBlock(JAVASCRIPTEXCEPTION, savedVariableObject,
catchLabel, startLabel);
/*
* catch WrappedExceptions, see if they are wrapped
* JavaScriptExceptions. Otherwise, rethrow.
*/
generateCatchBlock(WRAPPEDEXCEPTION, savedVariableObject,
catchLabel, startLabel);
/*
we also need to catch EcmaErrors and feed the
associated error object to the handler
*/
jsHandler = classFile.markHandler(acquireLabel());
exceptionObject = getNewWordLocal();
int jsHandler = classFile.markHandler(acquireLabel());
short exceptionObject = getNewWordLocal();
astore(exceptionObject);
aload(savedVariableObject);
astore(variableObjectLocal);
@ -2268,6 +2253,51 @@ public class Codegen extends Interpreter {
markLabel(realEnd);
}
private final int JAVASCRIPTEXCEPTION = 0;
private final int WRAPPEDEXCEPTION = 1;
private void generateCatchBlock(int exceptionType,
short savedVariableObject,
int catchLabel,
int startLabel)
{
int handler = classFile.markHandler(acquireLabel());
// MS JVM gets cranky if the exception object is left on the stack
short exceptionObject = getNewWordLocal();
astore(exceptionObject);
// reset the variable object local
aload(savedVariableObject);
astore(variableObjectLocal);
aload(exceptionObject);
releaseWordLocal(exceptionObject);
if (exceptionType == JAVASCRIPTEXCEPTION) {
// unwrap the exception...
addScriptRuntimeInvoke("unwrapJavaScriptException",
"(Lorg/mozilla/javascript/JavaScriptException;)",
"Ljava/lang/Object;");
} else {
// unwrap the exception...
addScriptRuntimeInvoke("unwrapWrappedException",
"(Lorg/mozilla/javascript/WrappedException;)",
"Ljava/lang/Object;");
}
String exceptionName = exceptionType == JAVASCRIPTEXCEPTION
? "org/mozilla/javascript/JavaScriptException"
: "org/mozilla/javascript/WrappedException";
// mark the handler
classFile.addExceptionHandler(startLabel, catchLabel, handler,
exceptionName);
addByteCode(ByteCode.GOTO, catchLabel);
}
private void visitThrow(Node node, Node child) {
visitStatement(node);
while (child != null) {