Skip to content

Commit 3a5222f

Browse files
authored
Fix missing unsafe available check (#6920)
1 parent 6d62e8b commit 3a5222f

File tree

3 files changed

+106
-1
lines changed

3 files changed

+106
-1
lines changed

exporters/common/build.gradle.kts

+3
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ testing {
6767
}
6868
}
6969
}
70+
suites {
71+
register<JvmTestSuite>("testWithoutUnsafe") {}
72+
}
7073
}
7174

7275
tasks {

exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/UnsafeString.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
class UnsafeString {
1111
private static final long valueOffset = getStringFieldOffset("value", byte[].class);
1212
private static final long coderOffset = getStringFieldOffset("coder", byte.class);
13-
private static final int byteArrayBaseOffset = UnsafeAccess.arrayBaseOffset(byte[].class);
13+
private static final int byteArrayBaseOffset =
14+
UnsafeAccess.isAvailable() ? UnsafeAccess.arrayBaseOffset(byte[].class) : -1;
1415
private static final boolean available = valueOffset != -1 && coderOffset != -1;
1516

1617
static boolean isAvailable() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.exporter.internal.marshal;
7+
8+
import static io.opentelemetry.exporter.internal.marshal.StatelessMarshalerUtil.getUtf8Size;
9+
import static io.opentelemetry.exporter.internal.marshal.StatelessMarshalerUtil.writeUtf8;
10+
import static org.assertj.core.api.Assertions.assertThat;
11+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
12+
13+
import java.io.ByteArrayOutputStream;
14+
import java.io.IOException;
15+
import java.io.InputStream;
16+
import java.nio.charset.StandardCharsets;
17+
import org.junit.jupiter.api.Test;
18+
19+
class StatelessMarshalerUtilTest {
20+
21+
// Simulate running in an environment without sun.misc.Unsafe e.g. when running a modular
22+
// application. To use sun.misc.Unsafe in modular application user would need to add dependency to
23+
// jdk.unsupported module or use --add-modules jdk.unsupported. Here we use a custom child first
24+
// class loader that does not delegate loading sun.misc classes to make sun.misc.Unsafe
25+
// unavailable.
26+
@Test
27+
void encodeUtf8WithoutUnsafe() throws Exception {
28+
ClassLoader testClassLoader =
29+
new ClassLoader(this.getClass().getClassLoader()) {
30+
@Override
31+
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
32+
// don't allow loading sun.misc classes
33+
if (name.startsWith("sun.misc")) {
34+
throw new ClassNotFoundException(name);
35+
}
36+
// load io.opentelemetry in the custom loader
37+
if (name.startsWith("io.opentelemetry")) {
38+
synchronized (this) {
39+
Class<?> clazz = findLoadedClass(name);
40+
if (clazz != null) {
41+
return clazz;
42+
}
43+
try (InputStream inputStream =
44+
getParent().getResourceAsStream(name.replace(".", "/") + ".class")) {
45+
if (inputStream != null) {
46+
byte[] bytes = readBytes(inputStream);
47+
// we don't bother to define packages or provide protection domain
48+
return defineClass(name, bytes, 0, bytes.length);
49+
}
50+
} catch (IOException exception) {
51+
throw new ClassNotFoundException(name, exception);
52+
}
53+
}
54+
}
55+
return super.loadClass(name, resolve);
56+
}
57+
};
58+
59+
// load test class in the custom loader and run the test
60+
Class<?> testClass = testClassLoader.loadClass(this.getClass().getName() + "$TestClass");
61+
assertThat(testClass.getClassLoader()).isEqualTo(testClassLoader);
62+
Runnable test = (Runnable) testClass.getConstructor().newInstance();
63+
test.run();
64+
}
65+
66+
private static byte[] readBytes(InputStream inputStream) throws IOException {
67+
ByteArrayOutputStream out = new ByteArrayOutputStream();
68+
byte[] buffer = new byte[1024];
69+
70+
int readCount;
71+
while ((readCount = inputStream.read(buffer, 0, buffer.length)) != -1) {
72+
out.write(buffer, 0, readCount);
73+
}
74+
return out.toByteArray();
75+
}
76+
77+
@SuppressWarnings("unused")
78+
public static class TestClass implements Runnable {
79+
80+
@Override
81+
public void run() {
82+
// verify that unsafe can't be found
83+
assertThatThrownBy(() -> Class.forName("sun.misc.Unsafe"))
84+
.isInstanceOf(ClassNotFoundException.class);
85+
// test the methods that use unsafe
86+
assertThat(getUtf8Size("a", true)).isEqualTo(1);
87+
assertThat(testUtf8("a", 0)).isEqualTo("a");
88+
}
89+
90+
static String testUtf8(String string, int utf8Length) {
91+
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
92+
CodedOutputStream codedOutputStream = CodedOutputStream.newInstance(outputStream);
93+
writeUtf8(codedOutputStream, string, utf8Length, true);
94+
codedOutputStream.flush();
95+
return new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
96+
} catch (Exception exception) {
97+
throw new IllegalArgumentException(exception);
98+
}
99+
}
100+
}
101+
}

0 commit comments

Comments
 (0)