From 4481700f653d2669b93e4f6fb1da2e4649584fb6 Mon Sep 17 00:00:00 2001 From: Simon Krajewski Date: Wed, 11 Dec 2024 07:35:53 +0100 Subject: [PATCH] haxe.Serializer cleanup (#11864) * reorganize a little * remove derpy looking cache juggling * bring back enum cache juggling * remove weird error thing * cast to Array on all targets Surely this is better than working with straight up Dynamic... --- std/haxe/Serializer.hx | 329 +++++++++++++-------------- tests/unit/src/unit/TestSerialize.hx | 197 +++++++++------- 2 files changed, 273 insertions(+), 253 deletions(-) diff --git a/std/haxe/Serializer.hx b/std/haxe/Serializer.hx index 411b85f744a..89c47ccdfcc 100644 --- a/std/haxe/Serializer.hx +++ b/std/haxe/Serializer.hx @@ -159,11 +159,6 @@ class Serializer { return; } shash.set(s, scount++); - #if old_serialize - // no more support for -D old_serialize due to 'j' reuse - #if error - #end - #end buf.add("y"); s = StringTools.urlEncode(s); buf.add(s.length); @@ -217,15 +212,15 @@ class Serializer { } /** - Serializes `v`. - - All haxe-defined values and objects with the exception of functions can - be serialized. Serialization of external/native objects is not - guaranteed to work. - - The values of `this.useCache` and `this.useEnumIndex` may affect - serialization output. -**/ + Serializes `v`. + + All haxe-defined values and objects with the exception of functions can + be serialized. Serialization of external/native objects is not + guaranteed to work. + + The values of `this.useCache` and `this.useEnumIndex` may affect + serialization output. + **/ public function serialize(v:Dynamic) { switch (Type.typeof(v)) { case TNull: @@ -250,38 +245,18 @@ class Serializer { } case TBool: buf.add(if (v) "t" else "f"); - case TClass(c): - if (#if neko untyped c.__is_String #else c == String #end) { - serializeString(v); - return; - } - if (useCache && serializeRef(v)) - return; - switch (#if (neko || python) Type.getClassName(c) #else c #end) { - case #if (neko || python) "Array" #else cast Array #end: - var ucount = 0; - buf.add("a"); - #if (flash || python || hl) - var v:Array = v; - #end - var l = #if (neko || flash || php || java || python || hl || lua || eval) v.length #elseif cpp v.__length() #else __getField(v, - "length") #end; - for (i in 0...l) { - if (v[i] == null) - ucount++; - else { - if (ucount > 0) { - if (ucount == 1) - buf.add("n"); - else { - buf.add("u"); - buf.add(ucount); - } - ucount = 0; - } - serialize(v[i]); - } - } + case TClass(String): + serializeString(v); + case TClass(_) if (useCache && serializeRef(v)): + case TClass(Array): + var ucount = 0; + buf.add("a"); + var v:Array = v; + var l = v.length; + for (i in 0...l) { + if (v[i] == null) + ucount++; + else { if (ucount > 0) { if (ucount == 1) buf.add("n"); @@ -289,125 +264,139 @@ class Serializer { buf.add("u"); buf.add(ucount); } + ucount = 0; } - buf.add("h"); - case #if (neko || python) "haxe.ds.List" #else cast List #end: - buf.add("l"); - var v:List = v; - for (i in v) - serialize(i); - buf.add("h"); - case #if (neko || python) "Date" #else cast Date #end: - var d:Date = v; - buf.add("v"); - buf.add(d.getTime()); - case #if (neko || python) "haxe.ds.StringMap" #else cast haxe.ds.StringMap #end: - buf.add("b"); - var v:haxe.ds.StringMap = v; - for (k in v.keys()) { - serializeString(k); - serialize(v.get(k)); - } - buf.add("h"); - case #if (neko || python) "haxe.ds.IntMap" #else cast haxe.ds.IntMap #end: - buf.add("q"); - var v:haxe.ds.IntMap = v; - for (k in v.keys()) { - buf.add(":"); - buf.add(k); - serialize(v.get(k)); - } - buf.add("h"); - case #if (neko || python) "haxe.ds.ObjectMap" #else cast haxe.ds.ObjectMap #end: - buf.add("M"); - var v:haxe.ds.ObjectMap = v; - for (k in v.keys()) { - #if (js || neko) - var id = Reflect.field(k, "__id__"); - Reflect.deleteField(k, "__id__"); - serialize(k); - Reflect.setField(k, "__id__", id); - #else - serialize(k); - #end - serialize(v.get(k)); - } - buf.add("h"); - case #if (neko || python) "haxe.io.Bytes" #else cast haxe.io.Bytes #end: - var v:haxe.io.Bytes = v; - #if neko - var chars = new String(base_encode(v.getData(), untyped BASE64.__s)); - buf.add("s"); - buf.add(chars.length); - buf.add(":"); - buf.add(chars); - #elseif php - var chars = new String(php.Global.base64_encode(v.getData())); - chars = php.Global.strtr(chars, '+/', '%:'); - buf.add("s"); - buf.add(chars.length); - buf.add(":"); - buf.add(chars); - #else - buf.add("s"); - buf.add(Math.ceil((v.length * 8) / 6)); - buf.add(":"); - - var i = 0; - var max = v.length - 2; - var b64 = BASE64_CODES; - if (b64 == null) { - b64 = new haxe.ds.Vector(BASE64.length); - for (i in 0...BASE64.length) - b64[i] = BASE64.charCodeAt(i); - BASE64_CODES = b64; - } - while (i < max) { - var b1 = v.get(i++); - var b2 = v.get(i++); - var b3 = v.get(i++); - - buf.addChar(b64[b1 >> 2]); - buf.addChar(b64[((b1 << 4) | (b2 >> 4)) & 63]); - buf.addChar(b64[((b2 << 2) | (b3 >> 6)) & 63]); - buf.addChar(b64[b3 & 63]); - } - if (i == max) { - var b1 = v.get(i++); - var b2 = v.get(i++); - buf.addChar(b64[b1 >> 2]); - buf.addChar(b64[((b1 << 4) | (b2 >> 4)) & 63]); - buf.addChar(b64[(b2 << 2) & 63]); - } else if (i == max + 1) { - var b1 = v.get(i++); - buf.addChar(b64[b1 >> 2]); - buf.addChar(b64[(b1 << 4) & 63]); - } - #end - default: - if (useCache) cache.pop(); - if (#if flash try - v.hxSerialize != null - catch (e:Dynamic) - false #elseif (java || python) Reflect.hasField(v, - "hxSerialize") #elseif php php.Global.method_exists(v, 'hxSerialize') #else v.hxSerialize != null #end) { - buf.add("C"); - serializeString(Type.getClassName(c)); - if (useCache) - cache.push(v); - v.hxSerialize(this); - buf.add("g"); - } else { - buf.add("c"); - serializeString(Type.getClassName(c)); - if (useCache) - cache.push(v); - #if flash - serializeClassFields(v, c); - #else - serializeFields(v); - #end - } + serialize(v[i]); + } + } + if (ucount > 0) { + if (ucount == 1) + buf.add("n"); + else { + buf.add("u"); + buf.add(ucount); + } + } + buf.add("h"); + case TClass(haxe.ds.List): + buf.add("l"); + var v:List = v; + for (i in v) + serialize(i); + buf.add("h"); + case TClass(haxe.ds.StringMap): + buf.add("b"); + var v:haxe.ds.StringMap = v; + for (k in v.keys()) { + serializeString(k); + serialize(v.get(k)); + } + buf.add("h"); + case TClass(haxe.ds.IntMap): + buf.add("q"); + var v:haxe.ds.IntMap = v; + for (k in v.keys()) { + buf.add(":"); + buf.add(k); + serialize(v.get(k)); + } + buf.add("h"); + case TClass(haxe.ds.ObjectMap): + buf.add("M"); + var v:haxe.ds.ObjectMap = v; + for (k in v.keys()) { + #if (js || neko) + var id = Reflect.field(k, "__id__"); + Reflect.deleteField(k, "__id__"); + serialize(k); + Reflect.setField(k, "__id__", id); + #else + serialize(k); + #end + serialize(v.get(k)); + } + buf.add("h"); + case TClass(Date): + var d:Date = v; + buf.add("v"); + buf.add(d.getTime()); + case TClass(haxe.io.Bytes): + var v:haxe.io.Bytes = v; + #if neko + var chars = new String(base_encode(v.getData(), untyped BASE64.__s)); + buf.add("s"); + buf.add(chars.length); + buf.add(":"); + buf.add(chars); + #elseif php + var chars = new String(php.Global.base64_encode(v.getData())); + chars = php.Global.strtr(chars, '+/', '%:'); + buf.add("s"); + buf.add(chars.length); + buf.add(":"); + buf.add(chars); + #else + buf.add("s"); + buf.add(Math.ceil((v.length * 8) / 6)); + buf.add(":"); + + var i = 0; + var max = v.length - 2; + var b64 = BASE64_CODES; + if (b64 == null) { + b64 = new haxe.ds.Vector(BASE64.length); + for (i in 0...BASE64.length) + b64[i] = BASE64.charCodeAt(i); + BASE64_CODES = b64; + } + while (i < max) { + var b1 = v.get(i++); + var b2 = v.get(i++); + var b3 = v.get(i++); + + buf.addChar(b64[b1 >> 2]); + buf.addChar(b64[((b1 << 4) | (b2 >> 4)) & 63]); + buf.addChar(b64[((b2 << 2) | (b3 >> 6)) & 63]); + buf.addChar(b64[b3 & 63]); + } + if (i == max) { + var b1 = v.get(i++); + var b2 = v.get(i++); + buf.addChar(b64[b1 >> 2]); + buf.addChar(b64[((b1 << 4) | (b2 >> 4)) & 63]); + buf.addChar(b64[(b2 << 2) & 63]); + } else if (i == max + 1) { + var b1 = v.get(i++); + buf.addChar(b64[b1 >> 2]); + buf.addChar(b64[(b1 << 4) & 63]); + } + #end + case TClass(c): + if ( + #if flash + try + v.hxSerialize != null + catch (e:Dynamic) + false + #elseif (java || python) + Reflect.hasField(v, "hxSerialize") + #elseif php + php.Global.method_exists(v, 'hxSerialize') + #else + v.hxSerialize != null + #end) { + buf.add("C"); + serializeString(Type.getClassName(c)); + v.hxSerialize(this); + buf.add("g"); + } else { + buf.add("c"); + serializeString(Type.getClassName(c)); + #if flash + serializeClassFields(v, c); + #else + serializeFields(v); + #end } case TObject: if (Std.isOfType(v, Class)) { @@ -573,12 +562,12 @@ class Serializer { } /** - Serializes `v` and returns the String representation. - - This is a convenience function for creating a new instance of - Serializer, serialize `v` into it and obtain the result through a call - to `toString()`. -**/ + Serializes `v` and returns the String representation. + + This is a convenience function for creating a new instance of + Serializer, serialize `v` into it and obtain the result through a call + to `toString()`. + **/ public static function run(v:Dynamic) { var s = new Serializer(); s.serialize(v); diff --git a/tests/unit/src/unit/TestSerialize.hx b/tests/unit/src/unit/TestSerialize.hx index dbf1027ee8b..0b95e3abfbd 100644 --- a/tests/unit/src/unit/TestSerialize.hx +++ b/tests/unit/src/unit/TestSerialize.hx @@ -1,83 +1,105 @@ package unit; +import haxe.Serializer; +import haxe.Unserializer; import haxe.ds.List; -class TestSerialize extends Test { +private enum Issue11864Enum { + B; + C(e1:Issue11864Enum, e2:Issue11864Enum); +} - function id( v : T ) : T { - return haxe.Unserializer.run(haxe.Serializer.run(v)); +class TestSerialize extends Test { + function id(v:T):T { + return Unserializer.run(Serializer.run(v)); } function test() { // basic types - var values : Array = [null, true, false, 0, 1, 1506, -0xABCDEF, 12.3, -1e10, "hello", "éé", "\r\n", "\n", " ", ""]; - for( v in values ) - eq( id(v), v ); - - t( Math.isNaN(id(Math.NaN)) ); - t( id(Math.POSITIVE_INFINITY) > 0 ); - f( id(Math.NEGATIVE_INFINITY) > 0 ); - f( Math.isFinite(id(Math.POSITIVE_INFINITY)) ); - f( Math.isFinite(id(Math.NEGATIVE_INFINITY)) ); + var values:Array = [ + null, + true, + false, + 0, + 1, + 1506, + -0xABCDEF, + 12.3, + -1e10, + "hello", + "éé", + "\r\n", + "\n", + " ", + "" + ]; + for (v in values) + eq(id(v), v); + + t(Math.isNaN(id(Math.NaN))); + t(id(Math.POSITIVE_INFINITY) > 0); + f(id(Math.NEGATIVE_INFINITY) > 0); + f(Math.isFinite(id(Math.POSITIVE_INFINITY))); + f(Math.isFinite(id(Math.NEGATIVE_INFINITY))); // array/list doTestCollection([]); - doTestCollection([1,2,4,5]); - doTestCollection([1,2,null,null,null,null,null,4,5]); + doTestCollection([1, 2, 4, 5]); + doTestCollection([1, 2, null, null, null, null, null, 4, 5]); // date var d = Date.now(); var d2 = id(d); - t( (d2 is Date) ); - eq( d2.toString(), d.toString() ); + t((d2 is Date)); + eq(d2.toString(), d.toString()); // object - var o = { x : "a", y : -1.56, z : "hello" }; + var o = {x: "a", y: -1.56, z: "hello"}; var o2 = id(o); - eq(o.x,o2.x); - eq(o.y,o2.y); - eq(o.z,o2.z); + eq(o.x, o2.x); + eq(o.y, o2.y); + eq(o.z, o2.z); // class instance var c = new MyClass(999); c.intValue = 33; c.stringValue = "Hello"; var c2 = id(c); - t( (c2 is MyClass) ); - f( c == c2 ); - eq( c2.intValue, c.intValue ); - eq( c2.stringValue, c.stringValue ); - eq( c2.get(), 999 ); + t((c2 is MyClass)); + f(c == c2); + eq(c2.intValue, c.intValue); + eq(c2.stringValue, c.stringValue); + eq(c2.get(), 999); // Class value - eq( id(MyClass), MyClass ); + eq(id(MyClass), MyClass); // enums - haxe.Serializer.USE_ENUM_INDEX = false; + Serializer.USE_ENUM_INDEX = false; doTestEnums(); - haxe.Serializer.USE_ENUM_INDEX = true; + Serializer.USE_ENUM_INDEX = true; doTestEnums(); // Enum value - eq( id(MyEnum), MyEnum ); + eq(id(MyEnum), MyEnum); // StringMap var h = new haxe.ds.StringMap(); - h.set("keya",2); - h.set("kéyb",-465); + h.set("keya", 2); + h.set("kéyb", -465); var h2 = id(h); - t( (h2 is haxe.ds.StringMap) ); - eq( h2.get("keya"), 2 ); - eq( h2.get("kéyb"), -465 ); - eq( Lambda.count(h2), 2 ); + t((h2 is haxe.ds.StringMap)); + eq(h2.get("keya"), 2); + eq(h2.get("kéyb"), -465); + eq(Lambda.count(h2), 2); // IntMap var h = new haxe.ds.IntMap(); - h.set(55,2); - h.set(-101,-465); + h.set(55, 2); + h.set(-101, -465); var h2 = id(h); - t( (h2 is haxe.ds.IntMap) ); - eq( h2.get(55), 2 ); - eq( h2.get(-101), -465 ); - eq( Lambda.count(h2), 2 ); + t((h2 is haxe.ds.IntMap)); + eq(h2.get(55), 2); + eq(h2.get(-101), -465); + eq(Lambda.count(h2), 2); // ObjectMap var h = new haxe.ds.ObjectMap(); @@ -113,85 +135,94 @@ class TestSerialize extends Test { doTestBytes(haxe.io.Bytes.ofString("ABCD")); doTestBytes(haxe.io.Bytes.ofString("héllé")); var b = haxe.io.Bytes.alloc(100); - for( i in 0...b.length ) - b.set(i,i%10); + for (i in 0...b.length) + b.set(i, i % 10); doTestBytes(b); doTestBytesCrossPlatform(); // recursivity c.ref = c; - haxe.Serializer.USE_CACHE = true; + Serializer.USE_CACHE = true; var c2 = id(c); - haxe.Serializer.USE_CACHE = false; - eq( c2.ref, c2 ); + Serializer.USE_CACHE = false; + eq(c2.ref, c2); // errors #if !cpp - exc(function() haxe.Unserializer.run(null)); + exc(function() Unserializer.run(null)); #end - exc(function() haxe.Unserializer.run("")); + exc(function() Unserializer.run("")); + } + function testEnumRef() { + var old = Serializer.USE_CACHE; + Serializer.USE_CACHE = true; + var e = C(B, B); + var e2 = id(e); + t(Type.enumEq(e, e2)); + Serializer.USE_CACHE = old; } function doTestEnums() { - eq( id(MyEnum.A), MyEnum.A ); - eq( id(MyEnum.B), MyEnum.B ); - var c = MyEnum.C(0,"hello"); - t( Type.enumEq( id(c), c ) ); - t( Type.enumEq( id(MyEnum.D(MyEnum.D(c))), MyEnum.D(MyEnum.D(c)) ) ); - t( (id(c) is MyEnum) ); - t(switch( id(c) ) { - case C(_,_): true; + eq(id(MyEnum.A), MyEnum.A); + eq(id(MyEnum.B), MyEnum.B); + var c = MyEnum.C(0, "hello"); + t(Type.enumEq(id(c), c)); + t(Type.enumEq(id(MyEnum.D(MyEnum.D(c))), MyEnum.D(MyEnum.D(c)))); + t((id(c) is MyEnum)); + t(switch (id(c)) { + case C(_, _): true; default: false; }); - eq( id(SimpleEnum.SE_A), SimpleEnum.SE_A ); - eq( id(SimpleEnum.SE_B), SimpleEnum.SE_B ); - eq( id(SimpleEnum.SE_C), SimpleEnum.SE_C ); - eq( id(SimpleEnum.SE_D), SimpleEnum.SE_D ); - t( id(SimpleEnum.SE_A) == SimpleEnum.SE_A ); + eq(id(SimpleEnum.SE_A), SimpleEnum.SE_A); + eq(id(SimpleEnum.SE_B), SimpleEnum.SE_B); + eq(id(SimpleEnum.SE_C), SimpleEnum.SE_C); + eq(id(SimpleEnum.SE_D), SimpleEnum.SE_D); + t(id(SimpleEnum.SE_A) == SimpleEnum.SE_A); } - function doTestCollection( a : Array ) { + function doTestCollection(a:Array) { var a2 = id(a); - eq( a2.length, a.length ); - for( i in 0...a.length ) - eq( a2[i], a[i] ); + eq(a2.length, a.length); + for (i in 0...a.length) + eq(a2[i], a[i]); var l = Lambda.list(a); var l2 = id(l); - t( (l2 is List) ); - eq( l2.length, l.length ); + t((l2 is List)); + eq(l2.length, l.length); var it = l.iterator(); - for( x in l2 ) - eq( x, it.next() ); - f( it.hasNext() ); + for (x in l2) + eq(x, it.next()); + f(it.hasNext()); } - function doTestBytes( b : haxe.io.Bytes ) { + function doTestBytes(b:haxe.io.Bytes) { var b2 = id(b); - t( (b2 is haxe.io.Bytes) ); - eq( b2.length, b.length ); - for( i in 0...b.length ) - eq( b2.get(i), b.get(i) ); + t((b2 is haxe.io.Bytes)); + eq(b2.length, b.length); + for (i in 0...b.length) + eq(b2.get(i), b.get(i)); } function doTestBytesCrossPlatform() { var sample = 's340:AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0%P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn%AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq%wsbKztLW2t7i5uru8vb6:wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t:g4eLj5OXm5%jp6uvs7e7v8PHy8:T19vf4%fr7:P3%'; - //serialization + // serialization var b = haxe.io.Bytes.alloc(255); - for(i in 0...255) b.set(i, i); - eq(sample, haxe.Serializer.run(b)); + for (i in 0...255) + b.set(i, i); + eq(sample, Serializer.run(b)); - //de-serialization - var b:haxe.io.Bytes = haxe.Unserializer.run(sample); + // de-serialization + var b:haxe.io.Bytes = Unserializer.run(sample); eq(255, b.length); - for(i in 0...b.length) { + for (i in 0...b.length) { var byte = b.get(i); eq(i, byte); - if(i != byte) break; + if (i != byte) + break; } } - }