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

Implement String.raw #879

Merged
merged 3 commits into from
May 7, 2021
Merged

Conversation

tonygermano
Copy link
Contributor

@tonygermano tonygermano commented May 2, 2021

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.

@rbri
Copy link
Collaborator

rbri commented May 2, 2021

Great, another step forward

Copy link
Collaborator

@gbrail gbrail left a 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);
Copy link
Collaborator

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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> 

Copy link
Collaborator

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.

Copy link
Contributor Author

@tonygermano tonygermano May 5, 2021

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?

Copy link
Collaborator

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.

Copy link
Contributor Author

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;

Copy link
Contributor Author

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;
Copy link
Collaborator

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.

Copy link
Contributor Author

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;
Copy link
Collaborator

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.

Copy link
Contributor Author

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?

@gbrail
Copy link
Collaborator

gbrail commented May 5, 2021

Thanks, this looks good -- I agree that we don't want to have a unit test that has more than 2^32 elements anyway.

@tonygermano tonygermano force-pushed the implement-string-raw branch 2 times, most recently from f438476 to b7d6281 Compare May 6, 2021 05:12
for (; ; ) {
/* step 8 a-i */
Object next;
next = ScriptRuntime.getObjectIndex(raw, nextIndex, cx);
Copy link
Contributor Author

@tonygermano tonygermano May 6, 2021

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.

@gbrail
Copy link
Collaborator

gbrail commented May 7, 2021

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
Test is marked as failing but it does not: test262/test/built-ins/DataView/prototype/getBigInt64/return-abrupt-from-tonumber-byteoffset-symbol.js
Test is marked as failing but it does not: test262/test/built-ins/RegExp/prototype/Symbol.match/builtin-failure-g-set-lastindex-err.js
Test is marked as failing but it does not: test262/test/built-ins/RegExp/prototype/Symbol.match/g-init-lastindex-err.js
Test is marked as failing but it does not: test262/test/built-ins/String/raw/length.js
Test is marked as failing but it does not: test262/test/built-ins/String/raw/name.js
Test is marked as failing but it does not: test262/test/built-ins/String/raw/raw.js
Test is marked as failing but it does not: test262/test/built-ins/String/raw/return-empty-string-from-empty-array-length.js
Test is marked as failing but it does not: test262/test/built-ins/String/raw/return-empty-string-if-length-is-negative-infinity.js
Test is marked as failing but it does not: test262/test/built-ins/String/raw/return-empty-string-if-length-is-not-defined.js
Test is marked as failing but it does not: test262/test/built-ins/String/raw/return-empty-string-if-length-is-undefined.js
Test is marked as failing but it does not: test262/test/built-ins/String/raw/return-empty-string-if-length-is-zero-NaN.js
Test is marked as failing but it does not: test262/test/built-ins/String/raw/return-empty-string-if-length-is-zero-boolean.js
Test is marked as failing but it does not: test262/test/built-ins/String/raw/return-empty-string-if-length-is-zero-null.js
Test is marked as failing but it does not: test262/test/built-ins/String/raw/return-empty-string-if-length-is-zero-or-less-number.js
Test is marked as failing but it does not: test262/test/built-ins/String/raw/return-empty-string-if-length-is-zero-or-less-string.js
Test is marked as failing but it does not: test262/test/built-ins/String/raw/return-the-string-value.js
Test is marked as failing but it does not: test262/test/built-ins/String/raw/returns-abrupt-from-next-key-toString.js
Test is marked as failing but it does not: test262/test/built-ins/String/raw/returns-abrupt-from-next-key.js
Test is marked as failing but it does not: test262/test/built-ins/String/raw/returns-abrupt-from-substitution.js
Test is marked as failing but it does not: test262/test/built-ins/String/raw/substitutions-are-appended-on-same-index.js
Test is marked as failing but it does not: test262/test/built-ins/String/raw/substitutions-are-limited-to-template-raw-length.js
Test is marked as failing but it does not: test262/test/built-ins/String/raw/template-length-throws.js
Test is marked as failing but it does not: test262/test/built-ins/String/raw/template-raw-throws.js
Test is marked as failing but it does not: test262/test/language/expressions/greater-than-or-equal/bigint-and-string.js
Test is marked as failing but it does not: test262/test/language/expressions/less-than/bigint-and-string.js

@tonygermano
Copy link
Contributor Author

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.

@tonygermano
Copy link
Contributor Author

test262 updates are now back in this PR. I think it's ready.

@gbrail
Copy link
Collaborator

gbrail commented May 7, 2021

Looks good -- thanks!

@gbrail gbrail merged commit 7d9ae82 into mozilla:master May 7, 2021
@tonygermano tonygermano deleted the implement-string-raw branch May 11, 2021 23:54
@p-bakker p-bakker added the feature Issues considered a new feature label Oct 13, 2021
@p-bakker p-bakker added this to the Release 1.7.14 milestone Oct 13, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Issues considered a new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants