Live data from Hacker News

Breaking java.lang.String

wouter.coekaerts.be

201–206 of 206 posts

Re: Breaking java.lang.String

#201

Earlier quoted context omitted.

My thinking is the same. I doubt this is an oversight. Making the String constructor thread-safe would likely slow things significantly. Great point about things being assumed not thread-safe. The JDK is pretty thorough with documenting thread-safety.

> Making the String constructor thread-safe would likely slow things significantly. By my reckoning it would speed things up, at least going by the Java code.

By what means? The only ways I would expect are either unconditionally duplicating the array or mutex, and I don’t think the mutex wouldn’t be simple. Adding a sync block on the input could be done but that’s assuming nothing else is locked on it (but probably points to there being a race condition elsewhere if there is..).

Unconditionally duplicating the array would use more memory and wouldn’t be faster.

Re: Breaking java.lang.String

#202

Earlier quoted context omitted.

> Making the String constructor thread-safe would likely slow things significantly. By my reckoning it would speed things up, at least going by the Java code.

By what means? The only ways I would expect are either unconditionally duplicating the array or mutex, and I don’t think the mutex wouldn’t be simple. Adding a sync block on the input could be done but that’s assuming nothing else is locked on it (but probably points to there being a race condition elsewhere if there is..). Unconditionally duplicating the array would use more memory and wouldn’t be faster.

Inlining `StringUTF16.compress` and `StringUTF16.bytes what the code currently does is essentially this (using python as pseudocode):

    latin1 = bytearray(len(input))
    for i in range(len(input)):
        c = input[i]
        if c > 255:
            latin1 = None
            break
        latin1[i] = c

    if latin1 is not None:
        return LATIN1, latin1

    utf16 = bytearray(len(input)*2)
    for i in range(0, len(input)):
        c = input[i]
        utf16[2*i] = c >> HI_BYTE_SHIFT 
        utf16[2*i+1] = c >> LO_BYTE_SHIFT

    return UTF16, utf16
The issue occurs because `input` can be mutated between the moment it finds a char that's above 255 in the first loop and the moment it visits that same char in the second loop.

The solution is to not do that, but instead something like:

    latin1 = bytearray(len(input))
    for i in range(len(input)):
        c = input[i]
        if c > 8
        for j in range(i+1, len(input)):
            c = input[j]
            utf16[2*i] = c
            utf16[2*i+1] = c >> 8
        return UTF16, utf16

    return LATIN1, latin1
This means if you find a char that's above 255 you will always append that char to the UTF16 array, there's no possibility that someone will change it under you because you append the exact same char you tested. So you can not get into the situation the essay describes, a utf16 string will always contain at least one non-latin1-code unit.

Non-vectorised performances should an improvement as latin1 to utf16 is a trivial operation (just copy every byte of the input to every other byte of the output)

Though if you vectorised the char to utf16 conversion you now vectorise two loops on bailout (latin1 -> utf16 up to i, then char -> utf16) which is probably less efficient. I don't know if the JDK has vectorised optimisations, the source has "HotSpotIntrinsicCandidate" annotations but I don't know to what extent the intrinsics go.

Re: Breaking java.lang.String

#203
post #134

Earlier quoted context omitted.

afaik the person you replied to is wrong, using System.arrayCopy would definitely make a copy and not be optimized away. Maybe poster meant only copying the reference with `char[] copy = original` which indeed does nothing, but that's not a defensive copy.

This copy has no external observable effects (it's only temporarily used in the constructor and never stored anywhere), and since the JVM is (under the JMM) permitted work under the assumption the original array doesn't change unless the thread itself changes it or there is some happens-before relationship with something that does, and is permitted to re-order same-thread execution as-if-serial, it's fully permitted…

You are incorrect - it is allowed to behave as if the original didn't or did change, that is true and in the memory model. But once the copy is done, then the state is settled, and is no longed allowed to change at any point. There are no more conflicting accesses.

It is "fully permitted to assume the original array is identical to the copied array" only to the extent that affects reading from the original array, not the copy (ie, it can pretend that the original didn't change, even it did).

Anything else would break causality requirements (JLS§17.4.8.).

Re: Breaking java.lang.String

#204

Earlier quoted context omitted.

Are you really going to put while(true) around every nearly every access of mutable state in your programs? That is the implication I get from your comment. I really think you should think hard before trying to dig your way out of a hole like that.

It's not very different from grabbing a mutex on the string. Except that someone is more likely to hang a mutex forever than to make this code hang forever. So yes that's fine. Why wouldn't it be? And "every time a string is copied" is pretty far from "nearly every access of mutable state". Also unless the string-changing code has a very elaborate timing attack, won't it only make the constructor loop again about hal…

This thread is honestly crazy to me. I've never seen so many people advocate for hot spin loops as a patchwork attempted solution to data races. Never mind have I never seen this recommended in any serious literature, I've never even seen it suggested in a random blog post, or in the worst code bases I've ever worked with (I have worked on some not great code in my professional experience in the past).

And the reason I am talking about mutable state, is because these issues are not specific to strings! Why do you think there's something special about these kinds of data races that make them specific to strings?

I really, genuinely hope you take some time to read more deeply into this topic, and talk with your peers about this some more, before you implement any of these ideas.

The fundamental lesson I think is that data races must be eliminated, and there is no solution that tolerates them or works around them. You must eliminate them from your code.

A key related lesson is that there is no unilateral way to do this for shared mutable state. If there is shared mutable state, 2 or more concurrent threads must co-operate in order to avoid data races (all threads must lock, or use atomics, or fences, etc.). "There is no unilateral solution", means there is no way to loop or lock or fence in just one thread, in order to fix or resolve or work around some other thread that does not.

Re: Breaking java.lang.String

#205

Earlier quoted context omitted.

It's not very different from grabbing a mutex on the string. Except that someone is more likely to hang a mutex forever than to make this code hang forever. So yes that's fine. Why wouldn't it be? And "every time a string is copied" is pretty far from "nearly every access of mutable state". Also unless the string-changing code has a very elaborate timing attack, won't it only make the constructor loop again about hal…

This thread is honestly crazy to me. I've never seen so many people advocate for hot spin loops as a patchwork attempted solution to data races. Never mind have I never seen this recommended in any serious literature, I've never even seen it suggested in a random blog post, or in the worst code bases I've ever worked with (I have worked on some not great code in my professional experience in the past). And the reason…

I didn't advocate it. I said the deadlock risk is not meaningful. You're badly misreading me.

Re: Breaking java.lang.String

#206

Earlier quoted context omitted.

This thread is honestly crazy to me. I've never seen so many people advocate for hot spin loops as a patchwork attempted solution to data races. Never mind have I never seen this recommended in any serious literature, I've never even seen it suggested in a random blog post, or in the worst code bases I've ever worked with (I have worked on some not great code in my professional experience in the past). And the reason…

I didn't advocate it. I said the deadlock risk is not meaningful. You're badly misreading me.

I'm genuinely glad you're not advocating for it. If your reasoning for that is not the same as mine, I hope it's equally critical.
Post reply on HN