JavaScript Array corruption with NaN values occational seen in Safari on iOS 10 beta 2

Originator:mtfrb123
Number:rdar://27299339 Date Originated:12-07-2016
Status:Open Resolved:
Product:Safari Product Version:iOS 10 beta 2
Classification: Reproducible:Yes
 
Under certain conditions, pertaining both to the exact code style (i.e. whether functions are inlined or not) and also just "random effects", array corruption are seen where elements of  a plain JavaScript array under certain conditions suddenly becomes NaN where an integer was expected. This was seen (among other places) in our application when doing ASN.1 conversion etc. However, I have managed to distill down a relatively simple test page that can quite reliably show the problem. The problem doesn't occur every time but the JavaScript code in the page can detect when it occurred and will keep refreshing the page until it occurs. On a non-affected browser, this means the page will keep refreshing. On an affected browser (such as Safari on iOS 10 beta 2 running on an iPhone 6) the refreshing will stop after some iterations (typically 5-10) and display error.

The program operates by creating an uint8array of size 8192 (it seems smaller array sizes causes the error to be more rare). It will fill this array with dummy values, then call "toStr" which first allocates a new plain Array, then copies the contents of the uint8array to the plain array, narrowing each element along the way. While doing this it builds up a string containing the original value and the copied value. When the error occurs, the element in the target array turns out to be NaN which should not be able to occur. The doTest() function tests for whether such a NaN value is contained in the resulting string which shows the error has occurred.

 Note that within each refresh the program runs 20 iterations and here it is also random when the error is seen. However, I have observed that if the error doesn't occur among the first set of iterations, it is not likely to occur at all and therefore it is necessary with this refreshing logic.

Note that the program logic itself is completely deterministic so every run should be the same, which is also the case on other browsers. This can be shown by removing the comments from the log() statement in the doTest() function so it prints out the array.

Note that the problem seems to go away if the function "narrow" is inlined rather than being a separate function (even though these two programs should be semantically equivalent). Also, if one omits the shift from "state >> 8" the error also seemingly goes away. Hence, it is critical the exact program structure is used when testing for the problem.

Steps to Reproduce:
Upload the attached HTML file to a web server, and open it on an affeced platform. The file is also attached.

<html>
<head>
<title>Array test page</title>
<script>
	log = function(s) {
		var ta = document.getElementById("ta");
		ta.value += s + "\n";
	}

	function narrow(x_0) {
		return x_0 << 24 >> 24;
	}

	function toStr(i8bytes) {
		var bytes, i, str;
		bytes = new Array(i8bytes.length);
		str = '';
		for (i = 0; i < 16; i++) {
			bytes[i] = narrow(i8bytes[i]);
			str += '*** (' + i8bytes[i] + ' - ' + bytes[i] + ')';
		}
		return str;
	}

	function doTest() {
		var sz = 8192;
		state = 0;
		var i;

		var fnd = false;
		for (i = 0; i < 20; i++) {
			var arr = new ArrayBuffer(sz);
			var i8bytes = new Uint8Array(arr, 0, sz);
			for (j = 0; j < i8bytes.length; j++) {
				state = state + 1;
				var v = state >> 8;
				i8bytes[j] = narrow(v);
			}
			var str = toStr(i8bytes);
			// log(str); <-- REMOVE COMMENT to show results
			if (str.indexOf("NaN") !== -1) {
				log("Found NaN at iteration" + i);
				log(str);
				fnd = true;
				break;
			}
		}
		return fnd;

	}
	function start() {
		log("Starting: " + new Date());
		if (!doTest()) {
			location.reload(true); // <--- REMOVE THIS LINE TO PREVENT RELOAD 
		}
	};
</script>
</head>
<body onload="start()">
<h1>Array test page</h1>
<p>Note that on a non-affected browser this page will reload indefinitely. On an affected browser, it
will stop reloading once the problem is detected.
<p>
<textarea id="ta" rows="10" cols="40"></textarea>
</body>
</html>

Expected Results:
The problem should run indefinitely.

Actual Results:
On an affected platform, the program stops and prints out the string it obtained, such as:

Found NaN at iteration6
*** (192 - NaN) *** (192 - NaN) *** (192 - NaN) ...

Version:
Version 10.0 (14A5297c), model: MG472QN/A, serial: C38NG6XJG5MN

Notes:


Configuration:
iPhone 6 with iOS 2 beta running on WiFi.

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!