blob: eeaa542bb8049d8aea38a0213e47efa542d307e7 [file] [log] [blame]
// Copyright 2018 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.unsafe;
import com.google.common.base.Preconditions;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Arrays;
import javax.annotation.Nullable;
import sun.misc.Unsafe;
/**
* Provides direct access to the string implementation used by JDK9.
*
* <p>Under JDK9, a string is two fields: <code>byte coder</code>, and <code>byte[] value</code>.
* The <code>coder</code> field has value 0 if the encoding is LATIN-1, and 2 if the encoding is
* UTF-16 (the classic JDK8 encoding).
*
* <p>The <code>value</code> field contains the actual bytes.
*/
public class StringUnsafe {
// Fields corresponding to the coder
public static final byte LATIN1 = 0;
public static final byte UTF16 = 1;
private static final StringUnsafe INSTANCE = initInstance();
private final Unsafe unsafe;
private final Constructor<String> constructor;
private final long valueOffset;
private final long coderOffset;
public static boolean canUse() {
return RuntimeVersion.isAtLeast9();
}
@Nullable
public static StringUnsafe getInstance() {
return Preconditions.checkNotNull(INSTANCE);
}
private static StringUnsafe initInstance() {
if (!canUse()) {
return null;
}
return new StringUnsafe();
}
private StringUnsafe() {
unsafe = UnsafeProvider.getInstance();
Field valueField;
Field coderField;
try {
this.constructor = String.class.getDeclaredConstructor(byte[].class, byte.class);
valueField = String.class.getDeclaredField("value");
coderField = String.class.getDeclaredField("coder");
} catch (ReflectiveOperationException e) {
throw new IllegalStateException(
"Bad fields/constructor: "
+ Arrays.toString(String.class.getDeclaredConstructors())
+ ", "
+ Arrays.toString(String.class.getDeclaredFields()),
e);
}
this.constructor.setAccessible(true);
valueField.setAccessible(true);
valueOffset = UnsafeProvider.getInstance().objectFieldOffset(valueField);
coderField.setAccessible(true);
coderOffset = UnsafeProvider.getInstance().objectFieldOffset(coderField);
}
/** Returns the coder used for this string. See {@link #LATIN1} and {@link #UTF16}. */
public byte getCoder(String obj) {
return unsafe.getByte(obj, coderOffset);
}
/**
* Returns the internal byte array, encoded according to {@link #getCoder}.
*
* <p>Use of this is unsafe. The representation may change from one JDK version to the next.
* Ensure you do not mutate this byte array in any way.
*/
public byte[] getByteArray(String obj) {
return (byte[]) unsafe.getObject(obj, valueOffset);
}
/** Constructs a new string from a byte array and coder. */
public String newInstance(byte[] bytes, byte coder) throws ReflectiveOperationException {
return constructor.newInstance(bytes, coder);
}
}