From 948658c9ec62f1a3d88803fed3fe4eba6f184ac7 Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Thu, 19 Mar 2020 14:52:59 +0800
Subject: [PATCH] Fix shadowed struct elements

---
 .../io/serialization/jce/JceDecoder.kt        |  9 +-
 .../io/serialization/jce/JceInput.kt          | 99 ++++++++++---------
 .../JceInputTest.kt                           | 42 ++++----
 3 files changed, 81 insertions(+), 69 deletions(-)

diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceDecoder.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceDecoder.kt
index 53ac4289e..dd963fc4e 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceDecoder.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceDecoder.kt
@@ -181,9 +181,9 @@ internal class JceDecoder(
 
     companion object {
         @Suppress("MemberVisibilityCanBePrivate")
-        val debuggingMode: Boolean by lazy { false }
+        var debuggingMode: Boolean = false
 
-        private var structureHierarchy: Int = 0
+        var structureHierarchy: Int = 0
 
         inline fun println(value: () -> String) {
             if (debuggingMode) {
@@ -258,7 +258,10 @@ internal class JceDecoder(
 
     override fun decodeSequentially(): Boolean = false
     override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
-        var jceHead = jce.currentHeadOrNull ?: return CompositeDecoder.READ_DONE
+        var jceHead = jce.currentHeadOrNull ?: kotlin.run {
+            println("decodeElementIndex: currentHead == null")
+            return CompositeDecoder.READ_DONE
+        }
 
         println { "decodeElementIndex: ${jce.currentHead}" }
         while (!jce.input.endOfInput) {
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceInput.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceInput.kt
index 1a3334fe7..595b41269 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceInput.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceInput.kt
@@ -116,62 +116,69 @@ internal class JceInput(
 
     @OptIn(ExperimentalUnsignedTypes::class)
     @PublishedApi
-    internal fun skipField(type: Byte): Unit = when (type) {
-        Jce.BYTE -> this.input.discardExact(1)
-        Jce.SHORT -> this.input.discardExact(2)
-        Jce.INT -> this.input.discardExact(4)
-        Jce.LONG -> this.input.discardExact(8)
-        Jce.FLOAT -> this.input.discardExact(4)
-        Jce.DOUBLE -> this.input.discardExact(8)
-        Jce.STRING1 -> this.input.discardExact(this.input.readUByte().toInt())
-        Jce.STRING4 -> this.input.discardExact(this.input.readInt())
-        Jce.MAP -> { // map
-            //println("skip map!")
-            nextHead()
-            repeat(skipToHeadAndUseIfPossibleOrFail(0, message = { "tag 0 not found when skipping map" }) {
-                readJceIntValue(it)
-            } * 2) {
-                val currentHead = currentHead
-                prepareNextHead()
-                skipField(currentHead.type)
+    internal fun skipField(type: Byte): Unit {
+        JceDecoder.println { "skipping ${JceHead.findJceTypeName(type)}" }
+        when (type) {
+            Jce.BYTE -> this.input.discardExact(1)
+            Jce.SHORT -> this.input.discardExact(2)
+            Jce.INT -> println("readInt=" + this.input.readInt())
+            Jce.LONG -> this.input.discardExact(8)
+            Jce.FLOAT -> this.input.discardExact(4)
+            Jce.DOUBLE -> this.input.discardExact(8)
+            Jce.STRING1 -> this.input.discardExact(this.input.readUByte().toInt())
+            Jce.STRING4 -> this.input.discardExact(this.input.readInt())
+            Jce.MAP -> { // map
+                JceDecoder.structureHierarchy++
+                var count: Int = 0
+                nextHead() // avoid shadowing, don't remove
+                repeat(skipToHeadAndUseIfPossibleOrFail(0, message = { "tag 0 not found when skipping map" }) {
+                    readJceIntValue(it).also { count = it * 2 }
+                } * 2) {
+                    skipField(currentHead.type)
+                    if (it != count - 1) { // don't read last head
+                        nextHead()
+                    }
+                }
+                JceDecoder.structureHierarchy--
             }
-        }
-        Jce.LIST -> { // list
-            JceDecoder.println {"skip list!"}
-            nextHead()
-            repeat(skipToHeadAndUseIfPossibleOrFail(0, message = { "tag 0 not found when skipping list" }) {
-                readJceIntValue(it)
-            }) {
-                val currentHead = currentHead
-                prepareNextHead()
-                skipField(currentHead.type)
+            Jce.LIST -> { // list
+                JceDecoder.structureHierarchy++
+                var count: Int = 0
+                nextHead() // avoid shadowing, don't remove
+                repeat(skipToHeadAndUseIfPossibleOrFail(0, message = { "tag 0 not found when skipping list" }) { head ->
+                    readJceIntValue(head).also { count = it }
+                }) {
+                    skipField(currentHead.type)
+                    if (it != count - 1) { // don't read last head
+                        nextHead()
+                    }
+                }
+                JceDecoder.structureHierarchy--
             }
-        }
-        Jce.STRUCT_BEGIN -> {
-            JceDecoder.println {"skip struct!"}
-            fun skipToStructEnd() {
+            Jce.STRUCT_BEGIN -> {
+                JceDecoder.structureHierarchy++
                 var head: JceHead
                 do {
                     head = nextHead()
                     skipField(head.type)
                 } while (head.type != Jce.STRUCT_END)
+                JceDecoder.structureHierarchy--
             }
-            skipToStructEnd()
-        }
-        Jce.STRUCT_END, Jce.ZERO_TYPE -> {
-            Unit
-        }
-        Jce.SIMPLE_LIST -> {
-            JceDecoder.println { "skip simple list!" }
-            var head = nextHead()
-            check(head.type == Jce.BYTE) { "bad simple list element type: " + head.type }
-            check(head.tag == 0) { "simple list element tag must be 0, but was ${head.tag}" }
+            Jce.STRUCT_END, Jce.ZERO_TYPE -> {
+            }
+            Jce.SIMPLE_LIST -> {
+                JceDecoder.structureHierarchy++
+                var head = nextHead()
+                check(head.type == Jce.BYTE) { "bad simple list element type: " + head.type }
+                check(head.tag == 0) { "simple list element tag must be 0, but was ${head.tag}" }
 
-            head = nextHead()
-            check(head.tag == 0) { "tag for size for simple list must be 0, but was ${head.tag}" }
-            this.input.discardExact(readJceIntValue(head))
+                head = nextHead()
+                check(head.tag == 0) { "tag for size for simple list must be 0, but was ${head.tag}" }
+                this.input.discardExact(readJceIntValue(head))
+                JceDecoder.structureHierarchy--
+            }
+            else -> error("invalid type: $type")
         }
-        else -> error("invalid type: $type")
     }
 
     // region readers
diff --git a/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceInputTest.kt b/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceInputTest.kt
index f00ac3cfc..f49753793 100644
--- a/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceInputTest.kt
+++ b/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceInputTest.kt
@@ -8,10 +8,7 @@ import kotlinx.io.core.writeFully
 import kotlinx.serialization.MissingFieldException
 import kotlinx.serialization.Serializable
 import net.mamoe.mirai.qqandroid.io.JceStruct
-import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce
-import net.mamoe.mirai.qqandroid.io.serialization.jce.JceId
-import net.mamoe.mirai.qqandroid.io.serialization.jce.JceInput
-import net.mamoe.mirai.qqandroid.io.serialization.jce.writeJceHead
+import net.mamoe.mirai.qqandroid.io.serialization.jce.*
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertFailsWith
@@ -42,6 +39,9 @@ internal const val ZERO_TYPE: Byte = 12
  */
 @Suppress("INVISIBLE_MEMBER") // bug
 internal class JceInputTest {
+    init {
+        JceDecoder.debuggingMode = true
+    }
 
     @Test
     fun testIntToStructMap() {
@@ -109,31 +109,33 @@ internal class JceInputTest {
         val input = buildPacket {
             writeJceHead(MAP, 0) // TestSerializableClassB
             writeJceHead(BYTE, 0)
-            writeByte(1)
+            writeByte(1);
 
-            writeJceHead(STRUCT_BEGIN, 0);
             {
-                writeJceHead(INT, 0)
-                writeInt(123)
+                writeJceHead(STRUCT_BEGIN, 0);
+                {
+                    writeJceHead(INT, 0)
+                    writeInt(123)
 
-                writeJceHead(STRUCT_BEGIN, 123); // TestSerializableClassC
+                    writeJceHead(STRUCT_BEGIN, 123); // TestSerializableClassC
+                    {
+                        writeJceHead(INT, 5)
+                        writeInt(123123)
+                    }()
+                    writeJceHead(STRUCT_END, 0)
+
+                    writeJceHead(INT, 5)
+                    writeInt(9)
+                }()
+                writeJceHead(STRUCT_END, 0)
+
+                writeJceHead(STRUCT_BEGIN, 1);
                 {
                     writeJceHead(INT, 5)
                     writeInt(123123)
                 }()
                 writeJceHead(STRUCT_END, 0)
-
-                writeJceHead(INT, 5)
-                writeInt(9)
             }()
-            writeJceHead(STRUCT_END, 0)
-
-            writeJceHead(STRUCT_BEGIN, 1);
-            {
-                writeJceHead(INT, 5)
-                writeInt(123123)
-            }()
-            writeJceHead(STRUCT_END, 0)
 
             writeJceHead(STRING1, 1)
             writeByte(1)