Loading core/java/android/net/Uri.java +41 −30 Original line number Diff line number Diff line Loading @@ -116,16 +116,27 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { private static final String LOG = Uri.class.getSimpleName(); /** * NOTE: EMPTY accesses this field during its own initialization, so this * field *must* be initialized first, or else EMPTY will see a null value! * * Placeholder for strings which haven't been cached. This enables us * Holds a placeholder for strings which haven't been cached. This enables us * to cache null. We intentionally create a new String instance so we can * compare its identity and there is no chance we will confuse it with * user data. * * NOTE This value is held in its own Holder class is so that referring to * {@link NotCachedHolder#NOT_CACHED} does not trigger {@code Uri.<clinit>}. * For example, {@code PathPart.<init>} uses {@code NotCachedHolder.NOT_CACHED} * but must not trigger {@code Uri.<clinit>}: Otherwise, the initialization of * {@code Uri.EMPTY} would see a {@code null} value for {@code PathPart.EMPTY}! * * @hide */ static class NotCachedHolder { private NotCachedHolder() { // prevent instantiation } @SuppressWarnings("RedundantStringConstructorCall") private static final String NOT_CACHED = new String("NOT CACHED"); static final String NOT_CACHED = new String("NOT CACHED"); } /** * The empty URI, equivalent to "". Loading Loading @@ -554,11 +565,11 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { return findSchemeSeparator() == NOT_FOUND; } private volatile String scheme = NOT_CACHED; private volatile String scheme = NotCachedHolder.NOT_CACHED; public String getScheme() { @SuppressWarnings("StringEquality") boolean cached = (scheme != NOT_CACHED); boolean cached = (scheme != NotCachedHolder.NOT_CACHED); return cached ? scheme : (scheme = parseScheme()); } Loading Loading @@ -968,11 +979,11 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { return -1; } private volatile String cachedString = NOT_CACHED; private volatile String cachedString = NotCachedHolder.NOT_CACHED; public String toString() { @SuppressWarnings("StringEquality") boolean cached = cachedString != NOT_CACHED; boolean cached = cachedString != NotCachedHolder.NOT_CACHED; if (cached) { return cachedString; } Loading Loading @@ -1102,11 +1113,11 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { return getUserInfoPart().getDecoded(); } private volatile String host = NOT_CACHED; private volatile String host = NotCachedHolder.NOT_CACHED; public String getHost() { @SuppressWarnings("StringEquality") boolean cached = (host != NOT_CACHED); boolean cached = (host != NotCachedHolder.NOT_CACHED); return cached ? host : (host = parseHost()); } Loading Loading @@ -1305,12 +1316,12 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { return this.path.getPathSegments(); } private volatile String uriString = NOT_CACHED; private volatile String uriString = NotCachedHolder.NOT_CACHED; @Override public String toString() { @SuppressWarnings("StringEquality") boolean cached = (uriString != NOT_CACHED); boolean cached = (uriString != NotCachedHolder.NOT_CACHED); return cached ? uriString : (uriString = makeUriString()); } Loading Loading @@ -1992,13 +2003,13 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { private final int mCanonicalRepresentation; AbstractPart(String encoded, String decoded) { if (encoded != NOT_CACHED) { if (encoded != NotCachedHolder.NOT_CACHED) { this.mCanonicalRepresentation = REPRESENTATION_ENCODED; this.encoded = encoded; this.decoded = NOT_CACHED; } else if (decoded != NOT_CACHED) { this.decoded = NotCachedHolder.NOT_CACHED; } else if (decoded != NotCachedHolder.NOT_CACHED) { this.mCanonicalRepresentation = REPRESENTATION_DECODED; this.encoded = NOT_CACHED; this.encoded = NotCachedHolder.NOT_CACHED; this.decoded = decoded; } else { throw new IllegalArgumentException("Neither encoded nor decoded"); Loading @@ -2009,7 +2020,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { final String getDecoded() { @SuppressWarnings("StringEquality") boolean hasDecoded = decoded != NOT_CACHED; boolean hasDecoded = decoded != NotCachedHolder.NOT_CACHED; return hasDecoded ? decoded : (decoded = decode(encoded)); } Loading @@ -2023,7 +2034,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { throw new IllegalArgumentException("Unknown representation: " + mCanonicalRepresentation); } if (canonicalValue == NOT_CACHED) { if (canonicalValue == NotCachedHolder.NOT_CACHED) { throw new AssertionError("Canonical value not cached (" + mCanonicalRepresentation + ")"); } Loading Loading @@ -2054,7 +2065,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { String getEncoded() { @SuppressWarnings("StringEquality") boolean hasEncoded = encoded != NOT_CACHED; boolean hasEncoded = encoded != NotCachedHolder.NOT_CACHED; return hasEncoded ? encoded : (encoded = encode(decoded)); } Loading Loading @@ -2085,7 +2096,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { * @param encoded part string */ static Part fromEncoded(String encoded) { return from(encoded, NOT_CACHED); return from(encoded, NotCachedHolder.NOT_CACHED); } /** Loading @@ -2094,7 +2105,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { * @param decoded part string */ static Part fromDecoded(String decoded) { return from(NOT_CACHED, decoded); return from(NotCachedHolder.NOT_CACHED, decoded); } /** Loading @@ -2105,7 +2116,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { */ static Part from(String encoded, String decoded) { // We have to check both encoded and decoded in case one is // NOT_CACHED. // NotCachedHolder.NOT_CACHED. if (encoded == null) { return NULL; Loading Loading @@ -2159,7 +2170,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { String getEncoded() { @SuppressWarnings("StringEquality") boolean hasEncoded = encoded != NOT_CACHED; boolean hasEncoded = encoded != NotCachedHolder.NOT_CACHED; // Don't encode '/'. return hasEncoded ? encoded : (encoded = encode(decoded, "/")); Loading Loading @@ -2265,7 +2276,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { * @param encoded part string */ static PathPart fromEncoded(String encoded) { return from(encoded, NOT_CACHED); return from(encoded, NotCachedHolder.NOT_CACHED); } /** Loading @@ -2274,7 +2285,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { * @param decoded part string */ static PathPart fromDecoded(String decoded) { return from(NOT_CACHED, decoded); return from(NotCachedHolder.NOT_CACHED, decoded); } /** Loading @@ -2301,7 +2312,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { */ static PathPart makeAbsolute(PathPart oldPart) { @SuppressWarnings("StringEquality") boolean encodedCached = oldPart.encoded != NOT_CACHED; boolean encodedCached = oldPart.encoded != NotCachedHolder.NOT_CACHED; // We don't care which version we use, and we don't want to force // unneccessary encoding/decoding. Loading @@ -2314,14 +2325,14 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { // Prepend encoded string if present. String newEncoded = encodedCached ? "/" + oldPart.encoded : NOT_CACHED; ? "/" + oldPart.encoded : NotCachedHolder.NOT_CACHED; // Prepend decoded string if present. @SuppressWarnings("StringEquality") boolean decodedCached = oldPart.decoded != NOT_CACHED; boolean decodedCached = oldPart.decoded != NotCachedHolder.NOT_CACHED; String newDecoded = decodedCached ? "/" + oldPart.decoded : NOT_CACHED; : NotCachedHolder.NOT_CACHED; return new PathPart(newEncoded, newDecoded); } Loading core/tests/coretests/src/android/net/UriTest.java +17 −0 Original line number Diff line number Diff line Loading @@ -136,6 +136,23 @@ public class UriTest extends TestCase { assertEquals(0, b.compareTo(b2)); } /** * Check that {@link Uri#EMPTY} is properly initialized to guard against a * regression based on a problematic initialization order (b/159907422). * * The problematic initialization order happened when {@code Uri$PathPart<clinit>} * ran before {@code Uri.<clinit>}. De facto this test would probably never have * failed on Android because {@code Uri.<clinit>} will almost certainly have run * somewhere in the Zygote, but just in case and in case this test is ever run on * a platform that doesn't perform Zygote initialization, this test attempts to * trigger {@code Uri$PathPart<clinit>} prior to inspecting {@link Uri#EMPTY}. */ @SmallTest public void testEmpty_initializerOrder() { new Uri.Builder().scheme("http").path("path").build(); assertEquals("", Uri.EMPTY.toString()); } @SmallTest public void testEqualsAndHashCode() { Loading Loading
core/java/android/net/Uri.java +41 −30 Original line number Diff line number Diff line Loading @@ -116,16 +116,27 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { private static final String LOG = Uri.class.getSimpleName(); /** * NOTE: EMPTY accesses this field during its own initialization, so this * field *must* be initialized first, or else EMPTY will see a null value! * * Placeholder for strings which haven't been cached. This enables us * Holds a placeholder for strings which haven't been cached. This enables us * to cache null. We intentionally create a new String instance so we can * compare its identity and there is no chance we will confuse it with * user data. * * NOTE This value is held in its own Holder class is so that referring to * {@link NotCachedHolder#NOT_CACHED} does not trigger {@code Uri.<clinit>}. * For example, {@code PathPart.<init>} uses {@code NotCachedHolder.NOT_CACHED} * but must not trigger {@code Uri.<clinit>}: Otherwise, the initialization of * {@code Uri.EMPTY} would see a {@code null} value for {@code PathPart.EMPTY}! * * @hide */ static class NotCachedHolder { private NotCachedHolder() { // prevent instantiation } @SuppressWarnings("RedundantStringConstructorCall") private static final String NOT_CACHED = new String("NOT CACHED"); static final String NOT_CACHED = new String("NOT CACHED"); } /** * The empty URI, equivalent to "". Loading Loading @@ -554,11 +565,11 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { return findSchemeSeparator() == NOT_FOUND; } private volatile String scheme = NOT_CACHED; private volatile String scheme = NotCachedHolder.NOT_CACHED; public String getScheme() { @SuppressWarnings("StringEquality") boolean cached = (scheme != NOT_CACHED); boolean cached = (scheme != NotCachedHolder.NOT_CACHED); return cached ? scheme : (scheme = parseScheme()); } Loading Loading @@ -968,11 +979,11 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { return -1; } private volatile String cachedString = NOT_CACHED; private volatile String cachedString = NotCachedHolder.NOT_CACHED; public String toString() { @SuppressWarnings("StringEquality") boolean cached = cachedString != NOT_CACHED; boolean cached = cachedString != NotCachedHolder.NOT_CACHED; if (cached) { return cachedString; } Loading Loading @@ -1102,11 +1113,11 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { return getUserInfoPart().getDecoded(); } private volatile String host = NOT_CACHED; private volatile String host = NotCachedHolder.NOT_CACHED; public String getHost() { @SuppressWarnings("StringEquality") boolean cached = (host != NOT_CACHED); boolean cached = (host != NotCachedHolder.NOT_CACHED); return cached ? host : (host = parseHost()); } Loading Loading @@ -1305,12 +1316,12 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { return this.path.getPathSegments(); } private volatile String uriString = NOT_CACHED; private volatile String uriString = NotCachedHolder.NOT_CACHED; @Override public String toString() { @SuppressWarnings("StringEquality") boolean cached = (uriString != NOT_CACHED); boolean cached = (uriString != NotCachedHolder.NOT_CACHED); return cached ? uriString : (uriString = makeUriString()); } Loading Loading @@ -1992,13 +2003,13 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { private final int mCanonicalRepresentation; AbstractPart(String encoded, String decoded) { if (encoded != NOT_CACHED) { if (encoded != NotCachedHolder.NOT_CACHED) { this.mCanonicalRepresentation = REPRESENTATION_ENCODED; this.encoded = encoded; this.decoded = NOT_CACHED; } else if (decoded != NOT_CACHED) { this.decoded = NotCachedHolder.NOT_CACHED; } else if (decoded != NotCachedHolder.NOT_CACHED) { this.mCanonicalRepresentation = REPRESENTATION_DECODED; this.encoded = NOT_CACHED; this.encoded = NotCachedHolder.NOT_CACHED; this.decoded = decoded; } else { throw new IllegalArgumentException("Neither encoded nor decoded"); Loading @@ -2009,7 +2020,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { final String getDecoded() { @SuppressWarnings("StringEquality") boolean hasDecoded = decoded != NOT_CACHED; boolean hasDecoded = decoded != NotCachedHolder.NOT_CACHED; return hasDecoded ? decoded : (decoded = decode(encoded)); } Loading @@ -2023,7 +2034,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { throw new IllegalArgumentException("Unknown representation: " + mCanonicalRepresentation); } if (canonicalValue == NOT_CACHED) { if (canonicalValue == NotCachedHolder.NOT_CACHED) { throw new AssertionError("Canonical value not cached (" + mCanonicalRepresentation + ")"); } Loading Loading @@ -2054,7 +2065,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { String getEncoded() { @SuppressWarnings("StringEquality") boolean hasEncoded = encoded != NOT_CACHED; boolean hasEncoded = encoded != NotCachedHolder.NOT_CACHED; return hasEncoded ? encoded : (encoded = encode(decoded)); } Loading Loading @@ -2085,7 +2096,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { * @param encoded part string */ static Part fromEncoded(String encoded) { return from(encoded, NOT_CACHED); return from(encoded, NotCachedHolder.NOT_CACHED); } /** Loading @@ -2094,7 +2105,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { * @param decoded part string */ static Part fromDecoded(String decoded) { return from(NOT_CACHED, decoded); return from(NotCachedHolder.NOT_CACHED, decoded); } /** Loading @@ -2105,7 +2116,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { */ static Part from(String encoded, String decoded) { // We have to check both encoded and decoded in case one is // NOT_CACHED. // NotCachedHolder.NOT_CACHED. if (encoded == null) { return NULL; Loading Loading @@ -2159,7 +2170,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { String getEncoded() { @SuppressWarnings("StringEquality") boolean hasEncoded = encoded != NOT_CACHED; boolean hasEncoded = encoded != NotCachedHolder.NOT_CACHED; // Don't encode '/'. return hasEncoded ? encoded : (encoded = encode(decoded, "/")); Loading Loading @@ -2265,7 +2276,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { * @param encoded part string */ static PathPart fromEncoded(String encoded) { return from(encoded, NOT_CACHED); return from(encoded, NotCachedHolder.NOT_CACHED); } /** Loading @@ -2274,7 +2285,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { * @param decoded part string */ static PathPart fromDecoded(String decoded) { return from(NOT_CACHED, decoded); return from(NotCachedHolder.NOT_CACHED, decoded); } /** Loading @@ -2301,7 +2312,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { */ static PathPart makeAbsolute(PathPart oldPart) { @SuppressWarnings("StringEquality") boolean encodedCached = oldPart.encoded != NOT_CACHED; boolean encodedCached = oldPart.encoded != NotCachedHolder.NOT_CACHED; // We don't care which version we use, and we don't want to force // unneccessary encoding/decoding. Loading @@ -2314,14 +2325,14 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { // Prepend encoded string if present. String newEncoded = encodedCached ? "/" + oldPart.encoded : NOT_CACHED; ? "/" + oldPart.encoded : NotCachedHolder.NOT_CACHED; // Prepend decoded string if present. @SuppressWarnings("StringEquality") boolean decodedCached = oldPart.decoded != NOT_CACHED; boolean decodedCached = oldPart.decoded != NotCachedHolder.NOT_CACHED; String newDecoded = decodedCached ? "/" + oldPart.decoded : NOT_CACHED; : NotCachedHolder.NOT_CACHED; return new PathPart(newEncoded, newDecoded); } Loading
core/tests/coretests/src/android/net/UriTest.java +17 −0 Original line number Diff line number Diff line Loading @@ -136,6 +136,23 @@ public class UriTest extends TestCase { assertEquals(0, b.compareTo(b2)); } /** * Check that {@link Uri#EMPTY} is properly initialized to guard against a * regression based on a problematic initialization order (b/159907422). * * The problematic initialization order happened when {@code Uri$PathPart<clinit>} * ran before {@code Uri.<clinit>}. De facto this test would probably never have * failed on Android because {@code Uri.<clinit>} will almost certainly have run * somewhere in the Zygote, but just in case and in case this test is ever run on * a platform that doesn't perform Zygote initialization, this test attempts to * trigger {@code Uri$PathPart<clinit>} prior to inspecting {@link Uri#EMPTY}. */ @SmallTest public void testEmpty_initializerOrder() { new Uri.Builder().scheme("http").path("path").build(); assertEquals("", Uri.EMPTY.toString()); } @SmallTest public void testEqualsAndHashCode() { Loading