We have a tool that looks for a scary noop case of assigning an instance field
to itself. this usually comes from a constructor that assigns a argument to a
instance field with the same name and then later the argument changes name. we
ran our tool on all of our classes we have in our classpath here and found this
problem in your code.
rhino1_5R2/src/org/mozilla/javascript/regexp/NativeRegExp.java line 159 it has:
this.flags = flags;
This seems to be a bad cut and paste from the CompilerState constructor on line
2155. or has some initialization that used to work been lost?
There is a bug in JavaScriptException which prevents it from being used with
out a Rhino Context. When the getMessage() method is invoked on it, the
exception goes to the ScriptRuntime to toString the value. If you have
already exited your context, the runtime will throw an error. The solution
is to simply remove the overridden getMessage method from
JavaScriptException. JavaScriptException's constructor calls the Exception
constructor with the toString'ed value. The default implementation of
getMessage will return the exception message.
Jeff
I'm having problems getting inner class objects with Rhino.
I create a Hashmap, which is an implementation of Map. Map.Entry is an
inner interface of Map with key-value pairs. If I have a Map object,
"property", I should be able to get the key element with the expression
"property.key".
When I look at the "property" class name that Rhino returns I get:
"java.util.HashMap$Entry". I don't believe Rhino has a notion of the
inner Map.Entry object. The expression "property" succeeds. The
expression "property.key", which should retrieve the Map.Entry
keyValue(), fails with a "unexpected IllegalAccessException accessing
Java field".
I'm including a simple example that illustrates the problem. I hope you
can shed some light on this. Thanks!
Justyna
< Justyna.Horwat@Sun.com >
----
import java.io.*;
import java.util.*;
import org.mozilla.javascript.*;
public class MapTest {
public static void main(String argv[]) {
Test test = new Test();
test.testMap();
}
}
class Test {
Map map;
Set set;
Iterator it;
Map.Entry entry;
public void testMap() {
System.out.println("testMap");
map = new HashMap();
populate();
set = map.entrySet();
it = set.iterator();
// let's see if Map is populated correctly
while (it.hasNext()) {
entry = (Map.Entry) it.next();
System.out.println("entry: " + entry.getClass().getName());
System.out.println("key: " + entry.getKey());
System.out.println("value: " + entry.getValue());
}
evaluate();
}
void populate() {
map.put("firstKey", "firstValue");
map.put("secondKey", "secondValue");
map.put("thirdKey", "thirdValue");
map.put("fourthKey", "fourthValue");
}
public void evaluate() {
Context cx = Context.enter();
Scriptable scope = cx.initStandardObjects(null);
set = map.entrySet();
it = set.iterator();
while (it.hasNext()) {
entry = (Map.Entry) it.next();
scope.put("property", scope, cx.toObject(entry,scope));
}
Object eval = null;
try {
// attempt to get Map.Entry key value using Rhino
eval = cx.evaluateString(scope, "property.key", "", 0,
null);
// Unwrap scoped object
if (eval instanceof Wrapper)
eval = ((Wrapper) eval).unwrap();
} catch (JavaScriptException jse) {
System.out.println("EXCEPTION: " + jse.getMessage());
}
// DELETE
System.out.println("RHINO result: " + eval + ":");
System.out.println("RHINO class: " + eval.getClass().getName());
}
}
with JSRESOLVE_ASSIGNING, wrongly), plus a few miscellaneous bugfixes.
- Combine the JSStackFrame members constructing, special, overrides, and
reserved into a uint32 flags member.
- Separate JOF_ASSIGNING from the JOF_SET bytecode format flag, and impute
JSRESOLVE_ASSIGNING from the presence of JOF_ASSIGNING among the current
opcode's format flags. To handle the for-in loop opcodes, which do more
than simply assign -- in particular, they do property lookups whose resolve
hook outcalls should not be flagged with JSRESOLVE_ASSIGNING -- a new frame
flag, JSFRAME_ASSIGNING, has been added.
- Fix interpreter version selection to respect JS_SetVersion, whose effect on
cx->version is "sticky".
- Fix js_DecompileValueGenerator to deal with JSOP_ENUMELEM -- it never had,
as this testcase shows (it crashes without this patch):
version(120);
eval("function fe(s) { for (it[s] in this); }");
try { fe('rdonly'); } catch (e) { print(e); }
My optimization for PreorderNodeIterator has a bug that would cause an attempt
to access stack[-1] in
currentParent = (current == null) ? null : stack[stackTop - 1];
when current refers to a start node sibling. This is not visible in Rhino because
currently PreorderNodeIterator is always started from nodes with node.next == null.
iter.diff fixes that plus it removes currentParent field because it is always
available as stack[stackTop - 1] and code to access its value are executed less
frequently than the lines to update it in nextNode
Regarsd, Igor
As profiler data show, the execution time of the nextNode and replaceCurrent
methods in PreorderNodeIterator contribute quite significantly to the total
time to run Context.compileReader.
replaceCurrent is slow because it calls Node.replaceChild which have to
iterate through all previous siblings to find the nearest to the current.
But it is easy to avoid this search by caching the previous sibling of the
current while iterating over the node tree in nextNode.
nextNode slowness is attributed to the usage of java.lang.Stack which is
expensive due to its synchronized methods. In the attched patch I replaced
it by the explicit array management.
It allows to cut Context.compileReader time by 5%-30% when processing
20K-3MB sources assembled form JS files in the test suite.
Note form omj/Parser.java:
* OPT source info collection is a potential performance bottleneck;
* Source wraps a java.lang.StringBuffer, which is synchronized. It
* might be faster to implement Source with its own char buffer and
* toString method.
It is indeed a bottleneck under JDK 1.1. When I replaced StringBuffer
by a char array (see the attached patch), execution time of
Context.compileReader decreased by 15%: to test I combined a few test
cases to get a 3MB JS source and then measured time to process it by
Context.compileReader in the interpreter mode.
Under JDK 1.3 the difference is less then 1%, but still using the explicit
string buffer saves memory. When converting StringBuffer to String Sun JDK
shares the internal char array in StringBuffer with new String, but in the
Parser case typically the capacity of this buffer is bigger then the actual
string length, so this unused space in source strings is wasted in the
interpreter mode that keeps these strings in InterpreterData.
Regards, Igor
========
I implemented that member expression as function name syntactic sugar to
support scripts using this MS extension. This is only available when
Context.hasFeature(Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME)
returns true to allow the deviation from the standard only when required.
The patch effectively transforms 'function <memberExpr>(...)...' to
'<memberExpr> = function(...)...' when <memberExpr> is not simple
identifier. I am not sure that MS implementation does exactly this
but hopefully it is sufficiently general to cover MS cases.
(The patch assumes that source_change.patch is already applied)
Regards, Igor
I implemented that member expression as function name syntactic sugar to support
scripts using this MS extension. This is only available when
Context.hasFeature(Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME)
returns true to allow the deviation from the standard only when required.
The patch effectively transforms 'function <memberExpr>(...)...' to
'<memberExpr> = function(...)...' when <memberExpr> is not simple identifier.
I am not sure that MS implementation does exactly this but hopefully it is
sufficiently general to cover MS cases.
(The patch assumes that source_change.patch is already applied)
Regards, Igor
Currently omj/TokenStream and omj/optimizer/Optimizer.java both contain
code to convert number value to a wrapper object of smallest size. The
attached patch moves this wrapping to Node constructor to avoid code
duplication and eliminate special treatment of exact integers in
Optimizer.java.
The constant folding code in omj/optimizer/Optimizer.java currently always
replaces x * 1, x - 0 by simply x which does not force the toNumber convertion,
which is visible, for example, via typeof. For example, when running at
optimization level 2, the following
function f() {
return "0" * 1;
}
print(typeof(f()));
prints "string" instead of expected "number".
The const_fold.patch fixes this via replacing x*1 by (+x) to force number convertion.
It assumes that the patch with number wrapping changes is in place.
convert number value to a wrapper object of smallest size. The attached patch
moves this wrapping to Node constructor to avoid code duplication and eliminate
special treatment of exact integers in Optimizer.java.
Currently omj/optimizer/Codegen.java uses special classes ConstantList
and ConstantDude to store the list of static constants in the generated
class. It seems that using a simple double[] array with a constant
counter and checking via "(int)number == number" for constant types not
only eliminates these 2 classes but makes the whole code simple, see
the attached patch.
The patch also modifies nodeIsConvertToObjectOfNumber to return not a
Number, but the number node itself that is used to extract double
value directly via Node.getDouble() call. I changed it to allow to
store values of number literals in nodes without using wrapper object.
Replacing usage of ShallowNodeIterator to loop throw node children by
explicit calls to Node.getFirstChild()/ Node.getNextSibling()) with
comments when the node children list is modified while iterating
through it.
It avoids creation of ShallowNodeIterator objects and eliminates the
need to have ShallowNodeIterator class.
Currently Rhino source has quite a few places with code like (String)node.getDatum()
or ((Number)node.getDatum()).doubleValue(). The patch changes this usage to call
node.getString() or node.getDouble().
It also adds new constructors to Node to accept int or double values in addition to
Object datum to replace new Node(token, new Integer(x)) by Node(token, x) etc. It
may allow in future not to create a wrapper object for int or double datum to speed
up parsing.
Currently in the interpreter mode all number literals are stored in
InterpreterData.itsICode as an index to InterpreterData.itsNumberTable
which holds the actual value.
For integers that fit 2 or 4 bytes this is an overkill and the attached
patch stores integers in InterpreterData.itsICode inline after special
TokenStream.INTNUMBER or TokenStream.SHORTNUMBERS tokens.
The changes made benchmarks to run 1.5% faster. It also saves memory
because InterpreterData.itsNumberTable is allocated only for non-integers
that present only in a small number of scripts.
In principle, it may be possible to store all numbers inline as well, but
unfortunately re-assembling of 8 bytes from InterpreterData.itsICode array
into double is rather slow operation and is not worth the hassles.
Regards, Igor
Hi, Norris!
Currently ScriptableObject.put does not check lastAccess cache during its search for
slots. When I added this check (see the attached patch) it speeded up the benchmark
suite by about 1.5% and in particular for setProp_bench.js the win was about 8%.
I think that even on multiprocessor machines it would not introduces any additional
issues like accessing the old value in the processor cache because the put method
accesses existing properties via unsynchronized getSlot, and the check for lastAccess
is on pair with that.
Trgards, Igor
When handling an Exception the Context tries to get the current script
and line number from the Java Stacktrace. To get the indication of which
entry in the trace might be an ECMA script, the file extension ".js" is
assumed.
For our integration we use the standard extension ".ecma" which collides
with the above assumption. But we don't force this extension, we just
have a convention. We name these files ".ecma" as they are not plain
ECMA but JSP-like ECMA. That is instead of using Java as the programming
language we use ECMA. In this respect they would be ".esp".