Skip to content

Commit

Permalink
Merge pull request #226 from simple-robot/dev/safer-dispatch-serializ…
Browse files Browse the repository at this point in the history
…er-resolve

更安全的 Signal.Dispatch 序列化方案
  • Loading branch information
ForteScarlet authored Oct 17, 2024
2 parents 0f41613 + 4b14314 commit 4afbb08
Show file tree
Hide file tree
Showing 22 changed files with 403 additions and 35 deletions.
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ apiValidation {
this.ignoredProjects.addAll(
listOf(
"api-reader",
"intents-processor"
"intents-processor",
"dispatch-serializer-processor",
),
)

Expand Down
47 changes: 47 additions & 0 deletions internal-processors/dispatch-serializer-processor/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2024. ForteScarlet.
*
* This file is part of simbot-component-qq-guild.
*
* simbot-component-qq-guild is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* simbot-component-qq-guild is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with simbot-component-qq-guild.
* If not, see <https://www.gnu.org/licenses/>.
*/

import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
kotlin("jvm")
}

repositories {
mavenCentral()
}

kotlin {
jvmToolchain(JVMConstants.KT_JVM_TARGET_VALUE)
compilerOptions {
javaParameters = true
jvmTarget.set(JvmTarget.fromTarget(JVMConstants.KT_JVM_TARGET_VALUE.toString()))
}
}

configJavaCompileWithModule()

dependencies {
api(libs.ksp)
api(libs.kotlinPoet.ksp)
testImplementation(kotlin("test-junit5"))
}

tasks.getByName<Test>("test") {
useJUnitPlatform()
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/*
* Copyright (c) 2024. ForteScarlet.
*
* This file is part of simbot-component-qq-guild.
*
* simbot-component-qq-guild is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* simbot-component-qq-guild is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with simbot-component-qq-guild.
* If not, see <https://www.gnu.org/licenses/>.
*/

package qg.internal.processors.dispatcherserializer

import com.google.devtools.ksp.getClassDeclarationByName
import com.google.devtools.ksp.isAbstract
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSFile
import com.google.devtools.ksp.symbol.Modifier
import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.ksp.toClassName
import com.squareup.kotlinpoet.ksp.writeTo
import java.time.Instant
import java.time.ZoneOffset

private const val SIGNAL_DISPATCH_PKG = "love.forte.simbot.qguild.event"
private const val SIGNAL_DISPATCH_SIMPLE_NAME = "Signal.Dispatch"
private const val SIGNAL_DISPATCH_NAME = "$SIGNAL_DISPATCH_PKG.$SIGNAL_DISPATCH_SIMPLE_NAME"

/**
*
* @author ForteScarlet
*/
internal class DispatchSerializerProcessor(
private val environment: SymbolProcessorEnvironment,
) : SymbolProcessor {
private data class SerializableDispatch(
val declaration: KSClassDeclaration,
val typeName: String
)

private lateinit var signalDispatchDeclaration: KSClassDeclaration

private val dispatchSerializableDeclarations = sortedSetOf<SerializableDispatch>(
Comparator.comparing { it.typeName }
)

override fun process(resolver: Resolver): List<KSAnnotated> {
signalDispatchDeclaration = resolver.getClassDeclarationByName(SIGNAL_DISPATCH_NAME)
?: error("Cannot find class declaration $SIGNAL_DISPATCH_NAME")

// find all subtypes,
resolver.getAllFiles()
.flatMap { it.declarations }
.filterIsInstance<KSClassDeclaration>()
.filter {
signalDispatchDeclaration.asStarProjectedType().isAssignableFrom(it.asStarProjectedType())
}
// not abstract
.filter {
!it.isAbstract() && !it.modifiers.contains(Modifier.SEALED)
}
// marked @kotlinx.serialization.Serializable
.filter {
it.annotations.any { anno ->
with(anno.annotationType.resolve().declaration) {
packageName.asString() == "kotlinx.serialization" &&
simpleName.asString() == "Serializable"
}
}
}
.mapNotNull {
// find @love.forte.simbot.qguild.event.DispatchTypeName
val typeNameAnnotation = it.annotations.find { anno ->
with(anno.annotationType.resolve().declaration) {
packageName.asString() == "love.forte.simbot.qguild.event" &&
simpleName.asString() == "DispatchTypeName"
}
} ?: return@mapNotNull null

val argument = typeNameAnnotation.arguments.find { a -> a.name?.asString() == "value" }
?: return@mapNotNull null

val typeName = argument.value as String

SerializableDispatch(it, typeName)
}
.toCollection(dispatchSerializableDeclarations)

return emptyList()
}

override fun finish() {
genResolver()
}

private fun genResolver() {
val orgFiles = mutableListOf<KSFile>()
signalDispatchDeclaration.containingFile?.also(orgFiles::add)

val fileSpecBuilder = FileSpec.builder("love.forte.simbot.qguild.event", "SignalDispatchResolvers.generated")

val funBuilder = FunSpec.builder("resolveDispatchSerializer").apply {
addAnnotation(ClassName("love.forte.simbot.qguild", "Generated"))
addParameter("eventName", String::class)
returns(
ClassName("kotlinx.serialization", "KSerializer")
.parameterizedBy(
WildcardTypeName
.producerOf(
ClassName(SIGNAL_DISPATCH_PKG, SIGNAL_DISPATCH_SIMPLE_NAME)
)
)
.copy(nullable = true)
)

addCode(
CodeBlock.builder().apply {
beginControlFlow("return when(eventName)")
// "" -> Type.serializer()
for (dispatchSerializableDeclaration in dispatchSerializableDeclarations) {
dispatchSerializableDeclaration.declaration.containingFile?.also(orgFiles::add)

addStatement(
"%S -> %T.serializer()",
dispatchSerializableDeclaration.typeName,
dispatchSerializableDeclaration.declaration.toClassName()
)
}
addStatement("else -> null")
endControlFlow()
}.build()
)

addKdoc(
CodeBlock.builder().apply {
addStatement("| event name | target |")
addStatement("| --- | --- |")
for (dispatchSerializableDeclaration in dispatchSerializableDeclarations) {
addStatement(
"| `%S` | [%T] |",
dispatchSerializableDeclaration.typeName,
dispatchSerializableDeclaration.declaration.toClassName()
)
}
addStatement("")
addStatement("@since 4.1.0")
}.build()
)
}

fileSpecBuilder.addFileComment("Auto generated at ${Instant.now().atOffset(ZoneOffset.ofHours(8))}")
fileSpecBuilder.addFunction(funBuilder.build())

fileSpecBuilder.build().writeTo(
environment.codeGenerator,
aggregating = true,
originatingKSFiles = orgFiles
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2024. ForteScarlet.
*
* This file is part of simbot-component-qq-guild.
*
* simbot-component-qq-guild is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* simbot-component-qq-guild is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with simbot-component-qq-guild.
* If not, see <https://www.gnu.org/licenses/>.
*/

package qg.internal.processors.dispatcherserializer

import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider

/**
*
* @author ForteScarlet
*/
class DispatchSerializerProcessorProvider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor =
DispatchSerializerProcessor(environment)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
qg.internal.processors.dispatcherserializer.DispatchSerializerProcessorProvider
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ rootProject.name = "qq-guild"

// internals
include(":internal-processors:api-reader")
include(":internal-processors:dispatch-serializer-processor")
include(":internal-processors:intents-processor")

//include(":builder-generator")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ public final class love/forte/simbot/qguild/ErrInfo$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public abstract interface annotation class love/forte/simbot/qguild/Generated : java/lang/annotation/Annotation {
}

public abstract interface annotation class love/forte/simbot/qguild/PrivateDomainOnly : java/lang/annotation/Annotation {
}

Expand Down Expand Up @@ -2434,6 +2437,10 @@ public final class love/forte/simbot/qguild/event/DirectMessageCreate$Companion
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public abstract interface annotation class love/forte/simbot/qguild/event/DispatchTypeName : java/lang/annotation/Annotation {
public abstract fun value ()Ljava/lang/String;
}

public final class love/forte/simbot/qguild/event/EventChannel {
public static final field Companion Llove/forte/simbot/qguild/event/EventChannel$Companion;
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Llove/forte/simbot/qguild/model/ChannelType;Llove/forte/simbot/qguild/model/ChannelSubType;Ljava/lang/String;Ljava/lang/String;)V
Expand Down Expand Up @@ -4373,6 +4380,7 @@ public final class love/forte/simbot/qguild/event/Signal$Companion {
public abstract class love/forte/simbot/qguild/event/Signal$Dispatch : love/forte/simbot/qguild/event/Signal {
public static final field Companion Llove/forte/simbot/qguild/event/Signal$Dispatch$Companion;
public static final field DISPATCH_CLASS_DISCRIMINATOR Ljava/lang/String;
public fun <init> ()V
public synthetic fun <init> (ILlove/forte/simbot/qguild/event/Opcode;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public abstract fun getData ()Ljava/lang/Object;
public abstract fun getId ()Ljava/lang/String;
Expand Down Expand Up @@ -4615,8 +4623,14 @@ public final class love/forte/simbot/qguild/event/Signal$Resume$Data$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public final class love/forte/simbot/qguild/event/SignalDispatchResolvers_generatedKt {
public static final fun resolveDispatchSerializer (Ljava/lang/String;)Lkotlinx/serialization/KSerializer;
}

public final class love/forte/simbot/qguild/event/SignalKt {
public static final fun getOpcode (Lkotlinx/serialization/json/JsonElement;)Ljava/lang/Integer;
public static final fun resolveDispatchSerializer (Lkotlinx/serialization/json/JsonObject;Z)Lkotlinx/serialization/KSerializer;
public static synthetic fun resolveDispatchSerializer$default (Lkotlinx/serialization/json/JsonObject;ZILjava/lang/Object;)Lkotlinx/serialization/KSerializer;
}

public final class love/forte/simbot/qguild/message/ArkBuilder {
Expand Down
1 change: 1 addition & 0 deletions simbot-component-qq-guild-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ kotlin {
dependencies {
add("kspJvm", project(":internal-processors:api-reader"))
add("kspCommonMainMetadata", project(":internal-processors:intents-processor"))
add("kspCommonMainMetadata", project(":internal-processors:dispatch-serializer-processor"))
}

ksp {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,10 @@ public annotation class QGApi4JS
@MustBeDocumented
@RequiresOptIn("Internal API", level = RequiresOptIn.Level.WARNING)
public annotation class QGInternalApi

/**
* A auto-generated API.
*/
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
public annotation class Generated
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,7 @@ public val EventIntentsInstances: Array<EventIntents>
*/
@Serializable
@SerialName(READY_TYPE)
@DispatchTypeName(READY_TYPE)
public data class Ready(
override val id: String? = null,
override val s: Long = DEFAULT_SEQ,
Expand All @@ -647,6 +648,7 @@ public data class Ready(
*/
@Serializable
@SerialName(RESUMED_TYPE)
@DispatchTypeName(RESUMED_TYPE)
public data class Resumed(
override val id: String? = null,
override val s: Long = DEFAULT_SEQ,
Expand Down
Loading

0 comments on commit 4afbb08

Please sign in to comment.