-
Notifications
You must be signed in to change notification settings - Fork 850
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
Implement String.raw #879
Implement String.raw #879
Conversation
Great, another step forward |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to cast around for a test suite that hits more code coverage? For instance:
https://github.com/v8/v8/blob/master/test/mjsunit/es6/string-raw.js
We've used a bunch of these in the "testsrc/jstests" directory. They don't always pass due to other things that we're missing, and when that happens I put a "TODO Rhino" comment around them, but that might help, since I see a bunch of the corner cases here not being covered. (And JavaScript, it turns out, is about 90% corner cases!)
Sorry, I am getting gradually more picky since I see so much good new stuff coming ;-)
/* step 8 a-i */ | ||
Object next; | ||
if (nextIndex > Integer.MAX_VALUE) { | ||
next = raw.get(Long.toString(nextIndex), raw); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you mind looking to see if there are any other tests, perhaps in the v8 project, that we could use to get better coverage? For example, I see that this case (of the index being greater than a 32-bit int) isn't being tested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can I actually just change nextIndex from a long to an int, and get rid of that step all together? It will be impossible to hit that number, because args
would have to be an Object[]
with Integer.MAX_VALUE+1
elements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I guess raw can have a length up to 2^53-1
, and if that happens, it will stop doing substitutions from args and use empty strings for the remainder of the substitutions. Do we really want a test that has to iterate that many times? It will probably cause an out of memory error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would get an out of memory error because either raw would have to have a massive amount of properties, or it is sparse, and the output will repeat "undefined" for each index that doesn't have an element.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just to confirm, I ran the following in a shell
Rhino 1.7.14-SNAPSHOT 2021 05 05
js> String.raw({raw:{length:5}})
undefinedundefinedundefinedundefinedundefined
js> String.raw({raw:{length:Math.pow(2,32)+1}})
java.lang.OutOfMemoryError: Java heap space
at java.base/java.util.Arrays.copyOf(Arrays.java:3745)
at java.base/java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:172)
at java.base/java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:538)
at java.base/java.lang.StringBuilder.append(StringBuilder.java:174)
at org.mozilla.javascript.NativeString.js_raw(NativeString.java:1145)
at org.mozilla.javascript.NativeString.execIdCall(NativeString.java:398)
at org.mozilla.javascript.IdFunctionObject.call(IdFunctionObject.java:100)
at org.mozilla.javascript.optimizer.OptRuntime.call1(OptRuntime.java:45)
at org.mozilla.javascript.gen._stdin__2._c_script_0(Unknown Source)
at org.mozilla.javascript.gen._stdin__2.call(Unknown Source)
at org.mozilla.javascript.ContextFactory.doTopCall(ContextFactory.java:415)
at org.mozilla.javascript.ScriptRuntime.doTopCall(ScriptRuntime.java:3603)
at org.mozilla.javascript.gen._stdin__2.call(Unknown Source)
at org.mozilla.javascript.gen._stdin__2.exec(Unknown Source)
at org.mozilla.javascript.tools.shell.Main.processSource(Main.java:497)
at org.mozilla.javascript.tools.shell.Main.processFiles(Main.java:181)
at org.mozilla.javascript.tools.shell.Main$IProxy.run(Main.java:101)
at org.mozilla.javascript.Context.call(Context.java:534)
at org.mozilla.javascript.ContextFactory.call(ContextFactory.java:525)
at org.mozilla.javascript.tools.shell.Main.exec(Main.java:163)
at org.mozilla.javascript.tools.shell.Main.main(Main.java:138)
js: exception from uncaught JavaScript throw: java.lang.OutOfMemoryError: Java heap space
js>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm find with leaving this untested, but it raises a bigger problem that we have in a lot of places -- JavaScript strings are supposed to have a maximum length of 2^53 but Java strings have a maximum length of 2^31. I don't think that we can change that all over Rhino. But if someone DOES try to create a raw string that's longer than that, then we'll likely leak a runtime exception from somewhere in Java.
I think we're better off here throwing an exception if the length is larger than the maximum integer size rather than let some other kind of exception bubble up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, if literalSegments > Integer.MAX_VALUE throw a RangeError? And then I could make nextIndex an int and remove this check?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! I think that would prevent worse problems later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One other question that I wasn't able to find an answer to by searching...
Is there a negative performance impact for checking if (a == b)
many times when one variable is an int and another is a long? My understanding is that the int is implicitly converted to a long. Would it be better to do this to avoid the conversions later, or does it not really matter?
long rawLength = NativeArray.getLengthProperty(cx, raw);
if (rawLength > Integer.MAX_VALUE) {/*throw RangeError*/}
int literalSegments = (int) rawLength;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went ahead and implemented it the way I asked about in my last question. In addition to removing the implicit conversion in the comparison, it also removed the need to explicitly cast long to int in two places further down in the code.
Scriptable cooked = ScriptRuntime.toObject(cx, scope, arg0); | ||
/* step 3 */ | ||
Object rawValue = cooked.get("raw", cooked); | ||
if (rawValue == NOT_FOUND) rawValue = undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW if I am reading "./gradlew jacocoTestReport" correctly, the "NOT_FOUND" case here isn't actually being tested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated this to use ScriptRuntime.getObjectProp
instead, which does the correct ecma [[Get]]
operation. It now searches up the prototype chain and returns undefined if not found in a single call. The test that covers raw being undefined is template-raw-not-object-throws.js
private static CharSequence js_raw(Context cx, Scriptable scope, Object[] args) { | ||
final Object undefined = Undefined.instance; | ||
/* step 1-2 */ | ||
Object arg0 = args.length > 0 ? args[0] : undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if the "undefined" case is being tested here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
template-not-object-throws.js
tests String.raw(undefined)
. Is that good enough or do you want a String.raw()
, too?
Thanks, this looks good -- I agree that we don't want to have a unit test that has more than 2^32 elements anyway. |
f438476
to
b7d6281
Compare
for (; ; ) { | ||
/* step 8 a-i */ | ||
Object next; | ||
next = ScriptRuntime.getObjectIndex(raw, nextIndex, cx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also changed from the original PR. Instead of using Scriptable.get(int Scriptable)
, it now uses ScriptRuntime.getObjectIndex
, which searches up the prototype chain and returns undefined when not found, rather than only searching the current object, and returning Scriptable.NOT_FOUND.
I included a test to make sure the prototype searching is working correctly.
The code looks great and thanks for the attention to detail. It looks like this fixes a bunch of test262 tests -- I can see the output here: Could you enable those String.raw tests in test262 in this PR, or if you're really tired of iterating, in another PR? Thanks! org.mozilla.javascript.tests.Test262SuiteTest STANDARD_OUT |
Oops. I had enabled all of the String.raw ones in the original PR, but I must have killed it in a rebase. I'll try to get that back in in a bit. I'll throw those few extra ones that aren't related to this PR in a separate commit so we don't need to make a whole new PR for them. |
b7d6281
to
2add1ce
Compare
test262 updates are now back in this PR. I think it's ready. |
Looks good -- thanks! |
Fixed merge conflict and bugs from 3rd commit of #81
Bug fixes were likely due to changes in spec since the original PR.
I separated this out since it isn't actually dependent on template literals.
All test262 tests are now passing except for the four which depend on template literals themselves.