Browse Source

Infinite recursion on stringification of objects with circular references (#8113)

* [lua] fixed stack overflow in stringification of recursive obejcts (fixes #7903)

* [java] fix infinite string recursion

see #8113

* [cs] fix infinite string recursion

see #8113

* added a test for recursive enums; [php] fixed stringification of recursive enums

* [flash] fixed infinite recrusin on stringification of recursive objects

* ...and a test for recursive arrays

* [java] fixed toString for recursive arrays

* [cs] fixed toString for recursive arrays

* [neko] fixed toString for recursive arrays
Aleksandr Kuzmenko 6 years ago
parent
commit
ed9b71e0fc

+ 19 - 0
std/cs/_std/Array.hx

@@ -30,6 +30,8 @@ final class Array<T> implements ArrayAccess<T> {
 
 	private var __a:NativeArray<T>;
 
+	@:skipReflection static var __hx_toString_depth = 0;
+
 #if erase_generics
 	inline private static function ofNative<X>(native:NativeArray<Dynamic>):Array<X>
 	{
@@ -309,6 +311,23 @@ final class Array<T> implements ArrayAccess<T> {
 	}
 
 	public function toString() : String
+	{
+		if (__hx_toString_depth >= 5) {
+			return "...";
+		}
+		++__hx_toString_depth;
+		try {
+			var s = __hx_toString();
+			--__hx_toString_depth;
+			return s;
+		} catch(e:Dynamic) {
+			--__hx_toString_depth;
+			throw(e);
+		}
+	}
+
+	@:skipReflection
+	function __hx_toString() : String
 	{
 		var ret = new StringBuf();
 		var a = __a;

+ 18 - 1
std/cs/internal/HxObject.hx

@@ -55,6 +55,8 @@ class DynamicObject extends HxObject
 	@:skipReflection var __hx_length_f:Int;
 	@:skipReflection var __hx_conflicts:FieldHashConflict;
 
+	@:skipReflection static var __hx_toString_depth = 0;
+
 	@:overload public function new()
 	{
 		this.__hx_hashes = new NativeArray(0);
@@ -241,7 +243,22 @@ class DynamicObject extends HxObject
 		return untyped fn.__hx_invokeDynamic(dynargs);
 	}
 
-	@:skipReflection public function toString():String
+	@:skipReflection public function toString() {
+		if (__hx_toString_depth >= 5) {
+			return "...";
+		}
+		++__hx_toString_depth;
+		try {
+			var s = __hx_toString();
+			--__hx_toString_depth;
+			return s;
+		} catch(e:Dynamic) {
+			--__hx_toString_depth;
+			throw(e);
+		}
+	}
+
+	@:skipReflection public function __hx_toString():String
 	{
 		var ts = Reflect.field(this, "toString");
 		if (ts != null)

+ 6 - 3
std/flash/Boot.hx

@@ -167,7 +167,10 @@ class Boot extends flash.display.MovieClip {
 		}
 	}
 
-	public static function __string_rec( v : Dynamic, str : String ) {
+	public static function __string_rec( v : Dynamic, str : String, maxRecursion : Int = 5 ) {
+		if(maxRecursion <= 0) {
+			return "<...>";
+		}
 		var cname = untyped __global__["flash.utils.getQualifiedClassName"](v);
 		switch( cname ) {
 		case "Object":
@@ -182,7 +185,7 @@ class Boot extends flash.display.MovieClip {
 					first = false;
 				else
 					s += ",";
-				s += " "+key+" : "+__string_rec(v[untyped key],str);
+				s += " "+key+" : "+__string_rec(v[untyped key],str,maxRecursion - 1);
 			}
 			if( !first )
 				s += " ";
@@ -200,7 +203,7 @@ class Boot extends flash.display.MovieClip {
 					first = false;
 				else
 					s += ",";
-				s += __string_rec(a[i],str);
+				s += __string_rec(a[i],str,maxRecursion - 1);
 			}
 			return s + "]";
 		default:

+ 17 - 0
std/java/_std/Array.hx

@@ -35,6 +35,8 @@ import java.NativeArray;
 
 	private var __a:NativeArray<T>;
 
+	@:skipReflection static var __hx_toString_depth = 0;
+
 	@:functionCode('
 			return new Array<X>(_native);
 	')
@@ -287,6 +289,21 @@ import java.NativeArray;
 
 	public function toString() : String
 	{
+		if (__hx_toString_depth >= 5) {
+			return "...";
+		}
+		++__hx_toString_depth;
+		try {
+			var s = __hx_toString();
+			--__hx_toString_depth;
+			return s;
+		} catch(e:Dynamic) {
+			--__hx_toString_depth;
+			throw(e);
+		}
+	}
+
+	function __hx_toString() : String {
 		var ret = new StringBuf();
 		var a = __a;
 		ret.add("[");

+ 17 - 0
std/java/internal/HxObject.hx

@@ -50,6 +50,8 @@ class DynamicObject extends HxObject
 	@:skipReflection var __hx_length:Int;
 	@:skipReflection var __hx_length_f:Int;
 
+	@:skipReflection static var __hx_toString_depth = 0;
+
 	@:overload public function new()
 	{
 		this.__hx_fields = new java.NativeArray(0);
@@ -200,6 +202,21 @@ class DynamicObject extends HxObject
 
 	public function toString():String
 	{
+		if (__hx_toString_depth >= 5) {
+			return "...";
+		}
+		++__hx_toString_depth;
+		try {
+			var s = __hx_toString();
+			--__hx_toString_depth;
+			return s;
+		} catch(e:Dynamic) {
+			--__hx_toString_depth;
+			throw(e);
+		}
+	}
+
+	function __hx_toString() {
 		var ts = this.__hx_getField("toString", false, false, false);
 		if (ts != null)
 			return ts();

+ 2 - 1
std/lua/Boot.hx

@@ -181,6 +181,7 @@ class Boot {
 	**/
 	@:ifFeature("has_enum")
 	static function __string_rec(o : Dynamic, s:String = "") {
+		if(s.length >= 5) return "<...>";
 		return switch(untyped __type__(o)){
 			case "nil": "null";
 			case "number" : {
@@ -219,7 +220,7 @@ class Boot {
 					for (f in fields){
 						if (first) first = false;
 						else Table.insert(buffer,", ");
-						Table.insert(buffer,'${Std.string(f)} : ${untyped Std.string(o[f])}');
+						Table.insert(buffer,'${Std.string(f)} : ${untyped __string_rec(o[f], s+"\t")}');
 					}
 					Table.insert(buffer, " }");
 					Table.concat(buffer, "");

+ 16 - 4
std/neko/_std/Array.hx

@@ -91,14 +91,26 @@
 		return s.toString();
 	}
 
+	static var __hx_toString_depth = 0;
+
 	public function toString() : String {
+		if(__hx_toString_depth >= 5) {
+			return "...";
+		}
 		var s = new StringBuf();
 		s.add("[");
 		var it = iterator();
-		for( i in it ) {
-			s.add(i);
-			if( it.hasNext() )
-				s.addChar(",".code);
+		__hx_toString_depth++;
+		try {
+			for( i in it ) {
+				s.add(i);
+				if( it.hasNext() )
+					s.addChar(",".code);
+			}
+			__hx_toString_depth--;
+		} catch(e:Dynamic) {
+			__hx_toString_depth--;
+			neko.Lib.rethrow(e);
 		}
 		s.add("]");
 		return s.toString();

+ 14 - 9
std/php/Boot.hx

@@ -359,6 +359,15 @@ class Boot {
 			if(Std.is(value, Array)) {
 				return inline stringifyNativeIndexedArray(value.arr, maxRecursion - 1);
 			}
+			if(Std.is(value, HxEnum)) {
+				var e:HxEnum = value;
+				var result = e.tag;
+				if (Global.count(e.params) > 0) {
+					var strings = Global.array_map(function (item) return Boot.stringify(item, maxRecursion - 1), e.params);
+					result += '(' + Global.implode(',', strings) + ')';
+				}
+				return result;
+			}
 			if (value.method_exists('toString')) {
 				return value.toString();
 			}
@@ -673,10 +682,11 @@ private class HxClass {
 **/
 @:keep
 @:dox(hide)
+@:allow(php.Boot.stringify)
 private class HxEnum {
-	var tag : String;
-	var index : Int;
-	var params : NativeArray;
+	final tag : String;
+	final index : Int;
+	final params : NativeArray;
 
 	public function new( tag:String, index:Int, arguments:NativeArray = null ) : Void {
 		this.tag = tag;
@@ -696,12 +706,7 @@ private class HxEnum {
 	**/
 	@:phpMagic
 	public function __toString() : String {
-		var result = tag;
-		if (Global.count(params) > 0) {
-			var strings = Global.array_map(function (item) return Boot.stringify(item), params);
-			result += '(' + Global.implode(',', strings) + ')';
-		}
-		return result;
+		return Boot.stringify(this);
 	}
 }
 

+ 43 - 0
tests/unit/src/unit/issues/Issue7903.hx

@@ -0,0 +1,43 @@
+package unit.issues;
+
+import utest.Assert;
+
+class Issue7903 extends unit.Test {
+	static var tmp:String;
+	function test() {
+		//Test recursive objects
+		var o = {rec:(null:Dynamic)};
+		o.rec = o;
+		try {
+			tmp = Std.string(o);
+			Assert.pass();
+		} catch(e:Dynamic) {
+			Assert.fail('Failed to stringify an object with recursive reference: $e');
+		}
+
+		//Test recursive enums
+		var o = {rec:(null:RecursiveEnum)};
+		var e = Ctor(o);
+		o.rec = e;
+		try {
+			tmp = Std.string(e);
+			Assert.pass();
+		} catch(e:Dynamic) {
+			Assert.fail('Failed to stringify an enum instance with recursive reference: $e');
+		}
+
+		//Test recursive arrays
+		var a:Array<Dynamic> = [];
+		a.push(a);
+		try {
+			tmp = Std.string(a);
+			Assert.pass();
+		} catch(e:Dynamic) {
+			Assert.fail('Failed to stringify a recursive array: $e');
+		}
+	}
+}
+
+private enum RecursiveEnum {
+	Ctor(o:{rec:Null<RecursiveEnum>});
+}