Skip to content

Commit

Permalink
Add sun.misc.Unsafe signatures
Browse files Browse the repository at this point in the history
Unsafe mostly works and is used in e.g. Guava and Protobuf-Javalite
ogolberg authored Dec 1, 2023
1 parent 08bae31 commit e76f7a3
Showing 22 changed files with 404 additions and 68 deletions.
64 changes: 41 additions & 23 deletions api/19/src/test/java/com/toasttab/android/Api19SignaturesTest.kt
Original file line number Diff line number Diff line change
@@ -20,33 +20,63 @@ import org.junit.Test
import strikt.api.expectThat
import strikt.assertions.all
import strikt.assertions.contains
import strikt.assertions.doesNotContain
import strikt.assertions.isA
import strikt.assertions.isNotNull
import java.io.File
import java.io.ObjectInputStream
import java.util.zip.GZIPInputStream

class Api19SignaturesTest {
companion object {
private fun signatures(name: String) =
ObjectInputStream(GZIPInputStream(File(System.getProperty(name)).inputStream())).use {
generateSequence { it.readObject() as Clazz? }.toList()
}

private val signatures by lazy {
signatures("signatures")
}

private val coreLibSignatures by lazy {
signatures("coreLibSignatures")
}

private val coreLibSignatures2 by lazy {
signatures("coreLibSignatures2")
}
}

@Test
fun `signatures include Integer#hashCode(int)`() {
val desc = ObjectInputStream(GZIPInputStream(File(System.getProperty("signatures")).inputStream())).use {
generateSequence { it.readObject() as Clazz? }.toList()
val integer = signatures.find { it.name == "java/lang/Integer" }

expectThat(integer).isNotNull().and {
get { signatures }.contains("hashCode(I)I")
}
}

val integer = desc.find { it.name == "java/lang/Integer" }
@Test
fun `signatures include Unsafe#getInt`() {
val integer = signatures.find { it.name == "sun/misc/Unsafe" }

expectThat(integer).isNotNull().and {
get { signatures }.contains("hashCode(I)I")
get { signatures }.contains("getInt(Ljava/lang/Object;J)I")
}
}

@Test
fun `core lib signatures include Stream#count()`() {
val desc = ObjectInputStream(GZIPInputStream(File(System.getProperty("coreLibSignatures")).inputStream())).use {
generateSequence { it.readObject() as Clazz? }.toList()
fun `signatures do not include Unsafe#storeFence`() {
val integer = signatures.find { it.name == "sun/misc/Unsafe" }

expectThat(integer).isNotNull().and {
get { signatures }.doesNotContain("storeFence()V")
}
}

val stream = desc.find { it.name == "java/util/stream/Stream" }
@Test
fun `core lib signatures include Stream#count()`() {
val stream = coreLibSignatures.find { it.name == "java/util/stream/Stream" }

expectThat(stream).isNotNull().and {
get { signatures }.contains("count()J")
@@ -55,11 +85,7 @@ class Api19SignaturesTest {

@Test
fun `core lib v2 signatures include Base64$Decoder#decode`() {
val desc = ObjectInputStream(GZIPInputStream(File(System.getProperty("coreLibSignatures2")).inputStream())).use {
generateSequence { it.readObject() as Clazz? }.toList()
}

val stream = desc.find { it.name == "java/util/Base64\$Decoder" }
val stream = coreLibSignatures2.find { it.name == "java/util/Base64\$Decoder" }

expectThat(stream).isNotNull().and {
get { signatures }.contains("decode([B)[B")
@@ -68,22 +94,14 @@ class Api19SignaturesTest {

@Test
fun `signatures use HashSet`() {
val desc = ObjectInputStream(GZIPInputStream(File(System.getProperty("signatures")).inputStream())).use {
generateSequence { it.readObject() as Clazz? }.toList()
}

expectThat(desc).all {
expectThat(signatures).all {
get { signatures }.isA<HashSet<*>>()
}
}

@Test
fun `core lib signatures use HashSet`() {
val desc = ObjectInputStream(GZIPInputStream(File(System.getProperty("coreLibSignatures")).inputStream())).use {
generateSequence { it.readObject() as Clazz? }.toList()
}

expectThat(desc).all {
expectThat(coreLibSignatures).all {
get { signatures }.isA<HashSet<*>>()
}
}
Original file line number Diff line number Diff line change
@@ -25,19 +25,35 @@ import protokt.v1.toasttab.expediter.v1.TypeExtensibility
import protokt.v1.toasttab.expediter.v1.TypeFlavor
import strikt.api.expectThat
import strikt.assertions.contains
import strikt.assertions.doesNotContain
import strikt.assertions.isEqualTo
import strikt.assertions.isNotNull
import java.io.File
import java.util.zip.GZIPInputStream

class Api19TypeDescriptorsTest {
@Test
fun `type descriptors include Integer#hashCode(int)`() {
val desc = GZIPInputStream(File(System.getProperty("platformDescriptors")).inputStream()).use {
TypeDescriptors.deserialize(it)
companion object {
private fun descriptors(name: String) =
GZIPInputStream(File(System.getProperty(name)).inputStream()).use {
TypeDescriptors.deserialize(it)
}

private val descriptors by lazy {
descriptors("platformDescriptors")
}

private val coreLibDescriptors by lazy {
descriptors("platformCoreLibDescriptors")
}

private val coreLibDescriptors2 by lazy {
descriptors("platformCoreLibDescriptors2")
}
}

val integer = desc.types.find { it.name == "java/lang/Integer" }
@Test
fun `type descriptors include Integer#hashCode(int)`() {
val integer = descriptors.types.find { it.name == "java/lang/Integer" }

expectThat(integer).isNotNull().and {
get { methods }.contains(
@@ -55,11 +71,7 @@ class Api19TypeDescriptorsTest {

@Test
fun `core lib type descriptors include Stream#count()`() {
val desc = GZIPInputStream(File(System.getProperty("platformCoreLibDescriptors")).inputStream()).use {
TypeDescriptors.deserialize(it)
}

val stream = desc.types.find { it.name == "java/util/stream/Stream" }
val stream = coreLibDescriptors.types.find { it.name == "java/util/stream/Stream" }

expectThat(stream).isNotNull().and {
get { methods }.contains(
@@ -80,11 +92,7 @@ class Api19TypeDescriptorsTest {
*/
@Test
fun `core lib v2 type descriptors include Base64$Decoder#decode`() {
val desc = GZIPInputStream(File(System.getProperty("platformCoreLibDescriptors2")).inputStream()).use {
TypeDescriptors.deserialize(it)
}

val decoder = desc.types.find { it.name == "java/util/Base64\$Decoder" }
val decoder = coreLibDescriptors2.types.find { it.name == "java/util/Base64\$Decoder" }

expectThat(decoder).isNotNull().and {
get { methods }.contains(
@@ -106,11 +114,7 @@ class Api19TypeDescriptorsTest {
*/
@Test
fun `core lib v2 LinuxFileSystemProvider extends UnixFileSystemProvider`() {
val desc = GZIPInputStream(File(System.getProperty("platformCoreLibDescriptors2")).inputStream()).use {
TypeDescriptors.deserialize(it)
}

val provider = desc.types.find { it.name == "sun/nio/fs/LinuxFileSystemProvider" }
val provider = coreLibDescriptors2.types.find { it.name == "sun/nio/fs/LinuxFileSystemProvider" }

expectThat(provider).isNotNull().and {
get { superName }.isEqualTo("sun/nio/fs/UnixFileSystemProvider")
@@ -123,11 +127,7 @@ class Api19TypeDescriptorsTest {
*/
@Test
fun `core lib v2 MimeTypesFileTypeDetector extends AbstractFileTypeDetector`() {
val desc = GZIPInputStream(File(System.getProperty("platformCoreLibDescriptors2")).inputStream()).use {
TypeDescriptors.deserialize(it)
}

val detector = desc.types.find { it.name == "sun/nio/fs/MimeTypesFileTypeDetector" }
val detector = coreLibDescriptors2.types.find { it.name == "sun/nio/fs/MimeTypesFileTypeDetector" }

expectThat(detector).isNotNull().and {
get { superName }.isEqualTo("sun/nio/fs/AbstractFileTypeDetector")
@@ -140,11 +140,7 @@ class Api19TypeDescriptorsTest {
*/
@Test
fun `core lib v2 IntStream is an interface`() {
val desc = GZIPInputStream(File(System.getProperty("platformCoreLibDescriptors2")).inputStream()).use {
TypeDescriptors.deserialize(it)
}

val stream = desc.types.find { it.name == "java/util/stream/IntStream" }
val stream = coreLibDescriptors2.types.find { it.name == "java/util/stream/IntStream" }

expectThat(stream).isNotNull().and {
get { flavor }.isEqualTo(TypeFlavor.INTERFACE)
@@ -158,14 +154,46 @@ class Api19TypeDescriptorsTest {
*/
@Test
fun `core lib v2 Character is final`() {
val desc = GZIPInputStream(File(System.getProperty("platformCoreLibDescriptors2")).inputStream()).use {
TypeDescriptors.deserialize(it)
}

val character = desc.types.find { it.name == "java/lang/Character" }
val character = coreLibDescriptors2.types.find { it.name == "java/lang/Character" }

expectThat(character).isNotNull().and {
get { extensibility }.isEqualTo(TypeExtensibility.FINAL)
}
}

@Test
fun `descriptors include Unsafe#getInt`() {
val integer = descriptors.types.find { it.name == "sun/misc/Unsafe" }

expectThat(integer).isNotNull().and {
get { methods }.contains(
MemberDescriptor {
ref = SymbolicReference {
name = "getInt"
signature = "(Ljava/lang/Object;J)I"
}
declaration = AccessDeclaration.INSTANCE
protection = AccessProtection.PUBLIC
}
)
}
}

@Test
fun `descriptors do not include Unsafe#storeFence`() {
val integer = descriptors.types.find { it.name == "sun/misc/Unsafe" }

expectThat(integer).isNotNull().and {
get { methods }.doesNotContain(
MemberDescriptor {
ref = SymbolicReference {
name = "storeFence"
signature = "()V"
}
declaration = AccessDeclaration.INSTANCE
protection = AccessProtection.PUBLIC
}
)
}
}
}
56 changes: 56 additions & 0 deletions api/24/src/test/java/com/toasttab/android/Api19SignaturesTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2023. Toast Inc.
*
* 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.toasttab.android

import org.codehaus.mojo.animal_sniffer.Clazz
import org.junit.Test
import strikt.api.expectThat
import strikt.assertions.contains
import strikt.assertions.isNotNull
import java.io.File
import java.io.ObjectInputStream
import java.util.zip.GZIPInputStream

class Api19SignaturesTest {
companion object {
private fun signatures(name: String) =
ObjectInputStream(GZIPInputStream(File(System.getProperty(name)).inputStream())).use {
generateSequence { it.readObject() as Clazz? }.toList()
}

private val signatures by lazy {
signatures("signatures")
}
}

@Test
fun `signatures include Unsafe#getInt`() {
val integer = signatures.find { it.name == "sun/misc/Unsafe" }

expectThat(integer).isNotNull().and {
get { signatures }.contains("getInt(Ljava/lang/Object;J)I")
}
}

@Test
fun `signatures include Unsafe#storeFence`() {
val integer = signatures.find { it.name == "sun/misc/Unsafe" }

expectThat(integer).isNotNull().and {
get { signatures }.contains("storeFence()V")
}
}
}
8 changes: 6 additions & 2 deletions buildSrc/src/main/kotlin/signatures-conventions.gradle.kts
Original file line number Diff line number Diff line change
@@ -56,7 +56,11 @@ dependencies {

add(Configurations.GENERATOR, project(":signature-builder"))

add(Configurations.STANDARD_SUGAR, project(":basic-sugar"))
add(Configurations.STANDARD_SUGAR, project(":sugar:basic"))
add(Configurations.STANDARD_SUGAR, project(":sugar:unsafe"))
if (project.name.toInt() >= 24) {
add(Configurations.STANDARD_SUGAR, project(":sugar:unsafe24"))
}
add(Configurations.EXERCISE_STANDARD_SUGAR, project(":test:basic-sugar-treadmill"))
add(Configurations.CORE_LIB_SUGAR, libs.desugarJdkLibs)
add(Configurations.CORE_LIB_SUGAR_2, libs.desugarJdkLibs2)
@@ -99,7 +103,7 @@ tasks {

systemProperty("dexout", layout.buildDirectory.path)

dependsOn(":basic-sugar:build")
dependsOn(":sugar:basic:build")
dependsOn(":test:basic-sugar-treadmill:build")

dependsOn(Tasks.signatures)
4 changes: 3 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -29,7 +29,9 @@ rootProject.name = "gummybears"
include(
"signature-builder",
"signature-transformer",
"basic-sugar",
"sugar:basic",
"sugar:unsafe",
"sugar:unsafe24",
"test:d8-runner",
"test:api-treadmill",
"test:basic-sugar-treadmill"
File renamed without changes.
File renamed without changes.
18 changes: 18 additions & 0 deletions sugar/unsafe/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (c) 2023. Toast Inc.
*
* 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.
*/

plugins {
`java-conventions`
}
90 changes: 90 additions & 0 deletions sugar/unsafe/src/main/java/desugar/sun/misc/DesugarUnsafe.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package desugar.sun.misc;

import java.lang.reflect.Field;

public final class DesugarUnsafe {
public <T> T allocateInstance(Class<T> clazz) {
return null;
}

public int arrayBaseOffset(Class<?> clazz) {
return 0;
}

public int arrayIndexScale(Class<?> clazz) {
return 0;
}

public boolean compareAndSwapInt(Object obj, long offset, int expectedValue, int newValue) {
return false;
}

public boolean compareAndSwapLong(Object obj, long offset, long expectedValue, long newValue) {
return false;
}

public boolean compareAndSwapObject(Object obj, long offset, Object expectedValue, Object newValue) {
return false;
}

public int getInt(Object obj, long offset) {
return 0;
}

public int getIntVolatile(Object obj, long offset) {
return 0;
}

public long getLong(Object obj, long offset) {
return 0;
}

public long getLongVolatile(Object obj, long offset) {
return 0;
}

public Object getObject(Object obj, long offset) {
return null;
}

public Object getObjectVolatile(Object obj, long offset) {
return null;
}

public long objectFieldOffset(Field field) {
return 0;
}

public void park(boolean absolute, long time) {
}

public void unpark(Object obj) {
}

public void putInt(Object obj, long offset, int newValue) {
}

public void putIntVolatile(Object obj, long offset, int newValue) {
}

public void putLong(Object obj, long offset, long newValue) {
}

public void putLongVolatile(Object obj, long offset, long newValue) {
}

public void putObject(Object obj, long offset, Object newValue) {
}

public void putObjectVolatile(Object obj, long offset, Object newValue) {
}

public void putOrderedInt(Object obj, long offset, int newValue) {
}

public void putOrderedLong(Object obj, long offset, long newValue) {
}

public void putOrderedObject(Object obj, long offset, Object newValue) {
}
}
18 changes: 18 additions & 0 deletions sugar/unsafe24/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (c) 2023. Toast Inc.
*
* 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.
*/

plugins {
`java-conventions`
}
98 changes: 98 additions & 0 deletions sugar/unsafe24/src/main/java/desugar/sun/misc/DesugarUnsafe.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package desugar.sun.misc;

import java.lang.reflect.Field;

public final class DesugarUnsafe {
public int addressSize() {
return 0;
}

public int pageSize() {
return 0;
}

public long allocateMemory(long bytes) {
return 0;
}

public void freeMemory(long address) {
}

public void setMemory(long address, long bytes, byte value) {
}

public byte getByte(long address) {
return 0;
}

public void putByte(Object obj, long offset, byte newValue) {
}

public short getShort(long address) {
return 0;
}

public void putShort(Object obj, long offset, short newValue) {
}

public char getChar(long address) {
return 0;
}

public void putChar(Object obj, long offset, char newValue) {
}

public int getInt(long address) {
return 0;
}

public long getLong(long address) {
return 0;
}

public float getFloat(long address) {
return 0;
}

public void putFloat(Object obj, long offset, float newValue) {
}

public double getDouble(long address) {
return 0;
}

public void putDouble(Object obj, long offset, double newValue) {
}

public void copyMemory(long srcAddr, long dstAddr, long bytes) {
}

public int getAndAddInt(Object o, long offset, int delta) {
return 0;
}

public long getAndAddLong(Object o, long offset, long delta) {
return 0;
}

public int getAndSetInt(Object o, long offset, int newValue) {
return 0;
}

public long getAndSetLong(Object o, long offset, long newValue) {
return 0;
}

public Object getAndSetObject(Object o, long offset, Object newValue) {
return 0;
}

public void loadFence() {
}

public void storeFence() {
}

public void fullFence() {
}
}
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@
package com.toasttab.android

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.options.multiple
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.required
import com.squareup.javapoet.ClassName
@@ -33,11 +34,12 @@ import javassist.bytecode.Descriptor
import javassist.bytecode.MethodInfo
import java.io.DataInputStream
import java.io.File
import java.lang.Exception
import java.util.jar.JarFile
import javax.lang.model.element.Modifier

class ApiUseGenerator : CliktCommand() {
private val jar: String by option(help = "jar with APIs").required()
private val jar: List<String> by option(help = "jar with APIs").multiple()
private val output: String by option(help = "output directory for generated classes").required()

private fun listClasses(jar: File): Sequence<ClassFile> {
@@ -70,14 +72,15 @@ class ApiUseGenerator : CliktCommand() {

return MethodSpec.methodBuilder(method.name)
.addModifiers(Modifier.PUBLIC)
.addException(Exception::class.java)
.returns(returnType.typeName())
.apply {
paramTypes.forEachIndexed { i, p ->
addParameter(p.typeName(), "arg$i")
}
}
.addCode(
"$instruction callee.${method.name}($params);\n"
"$instruction callee.${method.name}($params);"
)
.build()
}
@@ -106,7 +109,7 @@ class ApiUseGenerator : CliktCommand() {
}

override fun run() {
listClasses(File(jar)).forEach {
jar.flatMap { listClasses(File(it)) }.forEach {
write(it, File(output))
}
}
9 changes: 5 additions & 4 deletions test/basic-sugar-treadmill/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -23,19 +23,20 @@ configurations {
}

dependencies {
add(Configurations.STANDARD_SUGAR, project(":basic-sugar"))
add(Configurations.STANDARD_SUGAR, project(":sugar:basic"))
add(Configurations.STANDARD_SUGAR, project(":sugar:unsafe"))
add(Configurations.GENERATOR, project(":test:api-treadmill"))
}

tasks.register<JavaExec>("generateClasses") {
classpath = configurations.getByName(Configurations.GENERATOR).asFileTree
mainClass.set("com.toasttab.android.ApiUseGeneratorKt")
args = listOf(
"--jar",
configurations.getByName(Configurations.STANDARD_SUGAR).asPath,
"--output",
layout.buildDirectory.file("generated-sources/java/main").path
)
) + configurations.getByName(Configurations.STANDARD_SUGAR).flatMap {
listOf("--jar", it.path)
}
}

tasks.named<JavaCompile>("compileJava") {

0 comments on commit e76f7a3

Please sign in to comment.