Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ADD: delete method and set length property to NativeJavaList #1031

Merged
merged 2 commits into from
Oct 14, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions src/org/mozilla/javascript/NativeJavaList.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ public boolean has(int index, Scriptable start) {
return super.has(index, start);
}

public void delete(int index) {
if (isWithValidIndex(index)) {
list.set(index, null);
}
}

@Override
public boolean has(Symbol key, Scriptable start) {
if (SymbolKey.IS_CONCAT_SPREADABLE.equals(key)) {
Expand Down Expand Up @@ -87,6 +93,15 @@ public void put(int index, Scriptable start, Object value) {
super.put(index, start, value);
}

@Override
public void put(String name, Scriptable start, Object value) {
if (list != null && "length".equals(name)) {
setLength(value);
return;
}
super.put(name, start, value);
}

private void ensureCapacity(int minCapacity) {
if (minCapacity > list.size()) {
if (list instanceof ArrayList) {
Expand All @@ -98,6 +113,20 @@ private void ensureCapacity(int minCapacity) {
}
}

private void setLength(Object val) {
double d = ScriptRuntime.toNumber(val);
long longVal = ScriptRuntime.toUint32(d);
if (longVal != d || longVal > Integer.MAX_VALUE) {
String msg = ScriptRuntime.getMessageById("msg.arraylength.bad");
throw ScriptRuntime.rangeError(msg);
}
if (longVal < list.size()) {
list.subList((int) longVal, list.size()).clear();
} else {
ensureCapacity((int) longVal);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems dangerous since typical java Lists don't support sparse layouts like javascript arrays do. If you set length to a very large value on a javascript array it's no big deal, but do the same on an ArrayList, and you're probably going to run out of memory.

Should we at least add a javadoc description for the class that describes the additional behaviors which NativeJavaList adds to NativeJavaObject and potential pitfalls when using these features as if Lists were arrays? Is this stuff documented anywhere else other than in PRs and commit messages?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improving the Javadoc sounds great to me.

In general, the "embedding guide" is very very old and hasn't it been archived by Mozilla? Do we have any volunteers to resurrect it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How should we document this? JavaDoc, MD, wiki-page?

I would prefer a markdown documentation like this one: https://github.com/oracle/graaljs/blob/master/docs/user/JavaInteroperability.md

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I think the javadoc should contain basic important information for using the class. Wiki/user guide type documentation is good for going into more detail if someone is willing to write that. As we know, external documentation is not always easily accessible and developers should have some idea how a class works without having to closely study the code or pull up external docs.

}
}

@Override
public Object[] getIds() {
List<?> list = (List<?>) javaObject;
Expand Down
67 changes: 65 additions & 2 deletions testsrc/org/mozilla/javascript/tests/NativeJavaListTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import junit.framework.TestCase;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.EcmaError;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.tools.shell.Global;
Expand Down Expand Up @@ -65,6 +66,10 @@ public void testLengthProperty() {
list.add(2);
list.add(3);
assertEquals(3, runScriptAsInt("value.length", list));
runScriptAsInt("value.length = 6", list);
assertEquals(6, list.size());
runScriptAsInt("value.length = 2", list);
assertEquals(2, list.size());
}

public void testJavaMethodsCalls() {
Expand All @@ -83,8 +88,9 @@ public void testUpdatingJavaListIntegerValues() {
list.add(3);

assertEquals(2, runScriptAsInt("value[1]", list));
assertEquals(5, runScriptAsInt("value[1]=5;value[1]", list));
assertEquals(5, list.get(1).intValue());
// setting values in lists will set them as double
assertEquals("5.0", runScriptAsString("value[1]=5;value[1]", list));
assertEquals(5.0, list.get(1));
}

public void testAccessingJavaListStringValues() {
Expand All @@ -108,6 +114,63 @@ public void testUpdatingJavaListStringValues() {
assertEquals("f", list.get(1));
}

public void testAutoGrow() {
List<String> list = new ArrayList<>();
// Object list = runScript("[]", null, Function.identity());
assertEquals(0, runScriptAsInt("value.length", list));
assertEquals(1, runScriptAsInt("value[0]='a'; value.length", list));
assertEquals(3, runScriptAsInt("value[2]='c'; value.length", list));
assertEquals("a", runScriptAsString("value[0]", list));
// NativeArray will have 'undefined' here.
assertEquals("null", runScriptAsString("value[1]", list));
assertEquals("c", runScriptAsString("value[2]", list));
// NativeList will return "a,,c"
assertEquals("a,,c", runScriptAsString("Array.prototype.join.call(value)", list));
}

public void testLength() {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
runScriptAsString("value.length = 0", list);
assertEquals(0, list.size());
runScriptAsString("value.length = 10", list);
assertEquals(10, list.size());

try {
runScriptAsString("value.length = -10", list);
fail();
} catch (EcmaError e) {
assertEquals("RangeError: Inappropriate array length. (#1)", e.getMessage());
}

try {
runScriptAsString("value.length = 2.1", list);
fail();
} catch (EcmaError e) {
assertEquals("RangeError: Inappropriate array length. (#1)", e.getMessage());
}

try {
runScriptAsString("value.length = 2147483648", list); // Integer.MAX_VALUE + 1
fail();
} catch (EcmaError e) {
assertEquals("RangeError: Inappropriate array length. (#1)", e.getMessage());
}
}

public void testDelete() {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
// Object list = runScript("['a','b','c']", null, Function.identity());
// TODO: should NativeJavaList distinguish between 'null' and 'undefined'?
assertEquals("false", runScriptAsString("delete value[1]", list));
assertEquals("a,,c", runScriptAsString("Array.prototype.join.call(value)", list));
}

public void testKeys() {
List<String> list = new ArrayList<>();
NativeArray resEmpty =
Expand Down