Skip to content

Commit

Permalink
Make MessageChain truly read-only; fix #1676
Browse files Browse the repository at this point in the history
  • Loading branch information
Karlatemp committed Nov 15, 2021
1 parent a42a024 commit 4569036
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 1 deletion.
3 changes: 2 additions & 1 deletion mirai-core-api/src/commonMain/kotlin/message/data/impl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import net.mamoe.mirai.message.data.Image.Key.IMAGE_ID_REGEX
import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_1
import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_2
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.asImmutable
import net.mamoe.mirai.utils.replaceAllKotlin
import kotlin.native.concurrent.SharedImmutable

Expand Down Expand Up @@ -140,7 +141,7 @@ internal fun constrainSingleMessagesImpl(sequence: Sequence<SingleMessage>): Lis
}
}

return list.filterNotNull()
return list.filterNotNull().asImmutable()
}

@JvmSynthetic
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/

@file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST")

package net.mamoe.mirai.message.data

import org.junit.jupiter.api.Test
import kotlin.test.assertFails
import java.util.List as JdkList

internal open class MessageChainImmutableTest {
fun msg0(): MessageChain = messageChainOf(
AtAll, PlainText("Hello!"), At(114514),
)

fun msgAsJdk(): JdkList<SingleMessage> {
return msg0() as java.util.List<SingleMessage>
}

@Test
fun `direct access`() {
val chain = msgAsJdk()

assertFails { chain.set(0, AtAll) }
assertFails { chain.remove(0) }
assertFails { chain.clear() }
assertFails { chain.add(PlainText("Hey Hey!")) }
}

@Test
fun `iterator access`() {
val chain = msgAsJdk()
assertFails { chain.iterator().remove() }
assertFails { chain.iterator().also { it.next() }.remove() }
assertFails { chain.listIterator().remove() }
assertFails { chain.listIterator().also { it.next() }.remove() }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/

@file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST")

package net.mamoe.mirai.message.data

import org.junit.jupiter.api.Test
import kotlin.test.assertFails

internal class MessageChainImmutableTest_JDK8 : MessageChainImmutableTest() {
@Test
fun `access with JDK8 lambda`() {
val chain = msgAsJdk()
assertFails { chain.removeIf { true } }
assertFails { chain.replaceAll { AtAll } }
assertFails { chain.sort { o1, o2 -> o1.javaClass.name.compareTo(o2.javaClass.name) } }
assertFails { chain.clear() }
}
}
27 changes: 27 additions & 0 deletions mirai-core-utils/src/commonMain/kotlin/StandardUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,33 @@ public inline fun <E> MutableList<E>.replaceAllKotlin(operator: (E) -> E) {
}
}

public fun <T> Collection<T>.asImmutable(): Collection<T> {
return when (this) {
is List<T> -> asImmutable()
is Set<T> -> asImmutable()
else -> Collections.unmodifiableCollection(this)
}
}

@Suppress("NOTHING_TO_INLINE")
public inline fun <T> Collection<T>.asImmutableStrict(): Collection<T> {
return Collections.unmodifiableCollection(this)
}

@Suppress("NOTHING_TO_INLINE")
public inline fun <T> List<T>.asImmutable(): List<T> {
return Collections.unmodifiableList(this)
}

@Suppress("NOTHING_TO_INLINE")
public inline fun <T> Set<T>.asImmutable(): Set<T> {
return Collections.unmodifiableSet(this)
}

@Suppress("NOTHING_TO_INLINE")
public inline fun <K, V> Map<K, V>.asImmutable(): Map<K, V> {
return Collections.unmodifiableMap(this)
}

public fun Throwable.getRootCause(maxDepth: Int = 20): Throwable {
var depth = 0
Expand Down

0 comments on commit 4569036

Please sign in to comment.