Author: ptw
Date: 2007-10-15 20:33:27 -0700 (Mon, 15 Oct 2007)
New Revision: 6857
Modified:
openlaszlo/trunk/WEB-INF/lps/lfc/debugger/LzDebug.lzs
openlaszlo/trunk/WEB-INF/lps/lfc/debugger/platform/dhtml/LzDebug.js
openlaszlo/trunk/WEB-INF/lps/lfc/debugger/platform/swf/LzDebug.as
Log:
Change 20070930-ptw-P by [EMAIL PROTECTED] on 2007-09-30 18:01:29 EDT
in /Users/ptw/OpenLaszlo/ringding-2
for http://svn.openlaszlo.org/openlaszlo/trunk
Summary: Don't trust length as an indicator of array elements
Bugs Fixed:
LPP-4441 'Debugger misbehaves when debugging objects with "length" attribute.'
Technical Reviewer: [EMAIL PROTECTED] (Message-ID: <[EMAIL PROTECTED]>)
QA Reviewer: hminsky (pending)
Doc Reviewer: jsundman (pending)
Documentation:
NOTE: If you have Firebug enabled in Firefox, the LZX debugger echos
all messages to the Firebug console, preserving objects. The Firebug
debugger will attempt to interpret an object with a `length` field as
an array and try to print every array element. This may cause a
'Script Running Slowly' error. Disabling Firebug will prevent that.
Details:
Don't trust length to indicate how many properties are in an
object (or elements in an array). Instead, get the properties
from the object using `for ... in`. We still present any object
with a `length` property that is non-negative as an array, but we
won't try to iterate through the entire array. This is useful for
sparse arrays, but also for objects that just happen to have a
non-negative length property.
LzDebug.lzs: Added Debug.objectOwnProperties that gets the names
of all the 'own' properties of the object. If the object has a
non-negative `length` property, accumulate the properties that are
between 0 and that length in a separate list of indices.
LzDebug.{as,js}: Use that to print and inspect objects and arrays
without falling into the trap of iterating from 0 to length.
Basically, the old presentation style is preserved, but we iterate
_only_ over the properties the object actually has.
Tests:
lzx> Debug.write({1: 'one', 97: 'ninety-seven', a: 'eh?', floogle: 'snort',
length: 100000000, __proto__: {bar: 'bletch'}})
?\194?\171Object(100000000)#127| [..., one, ..., ninety-seven]?\194?\187
lzx> Debug.inspect([..., one, ..., ninety-seven])
?\194?\171Object(100000000)#127| [..., one, ..., ninety-seven]?\194?\187 {
a: 'eh?'
floogle: 'snort'
length: 100000000
1: 'one'
97: 'ninety-seven'
}
[..., one, ..., ninety-seven]
lzx> Debug.write({length:'0'})
{length: 0}
lzx> Debug.write({length:0})
?\194?\171Object(0)#174| []?\194?\187
lzx>
Above completes without "Script Running Slowly". Tested in SWF
and DHTML (with Firebug disabled, see Documentation NOTE above).
smokecheck passes in DHTML and SWF (which tests Debug.format,
which uses __String, showing that the expected presentation is preserved).
Modified: openlaszlo/trunk/WEB-INF/lps/lfc/debugger/LzDebug.lzs
===================================================================
--- openlaszlo/trunk/WEB-INF/lps/lfc/debugger/LzDebug.lzs 2007-10-16
02:46:17 UTC (rev 6856)
+++ openlaszlo/trunk/WEB-INF/lps/lfc/debugger/LzDebug.lzs 2007-10-16
03:33:27 UTC (rev 6857)
@@ -464,20 +464,23 @@
};
/**
- * Fills two arrays with the object's own properties. If the object has a
- * 'length' property (i.e., might be intended as an Array), numeric
- * names that fall between 0 and the value of length are added to the
- * `indices` array, otherwise they are added to the `names` array. If
- * either array is null, those properties will be omitted altogether.
- * @param obj:Object the object to examine
- * @param names:Array the array to append names to
- * @param indices:Array the array to append indices to
- * @param limit:Number don't accumulate more than this many properties
- * (used to limit computation on large objects), default Infinity
+ * Fills two arrays with the object's own properties. If the object
+ * has a non-negative integer 'length' property (i.e., might be
+ * intended as an Array), numeric names that fall between 0 and the
+ * value of length are added to the `indices` array, otherwise they
+ * are added to the `names` array. If either array is null, those
+ * properties will be omitted altogether. @param obj:Object the
+ * object to examine @param names:Array the array to append names to
+ * @param indices:Array the array to append indices to @param
+ * limit:Number don't accumulate more than this many properties (used
+ * to limit computation on large objects), default Infinity
*/
Debug.objectOwnProperties = function (obj, names, indices, limit) {
if (!limit) { limit = Infinity; };
- var alen = (('length' in obj) && (! isNaN(obj.length)) && (obj.length >= 0))
? obj.length : false;
+ // Check for 'array-ness'
+ var alen = (('length' in obj) &&
+ (Math.floor(obj.length) === obj.length) &&
+ (obj.length >= 0)) ? obj.length : false;
var hopp = 'hasOwnProperty' in obj;
var proto = (('__proto__' in obj) ? obj.__proto__ :
(('constructor' in obj) ? obj.constructor.prototype : false));
@@ -487,7 +490,10 @@
// Heuristic to find getter slots (there is no way to ask if a
// property is a getter)
(proto && (obj[key] !== proto[key]))) {
- if ((alen != false) && (! isNaN(key)) && (0 <= key) && (key < alen)) {
+ if ((alen != false) &&
+ // Only `==` here because all keys are strings
+ (Math.floor(key) == key) &&
+ (0 <= key) && (key < alen)) {
if (indices) {
// Ensure indices are numbers, not strings
indices.push(Number(key));
Modified: openlaszlo/trunk/WEB-INF/lps/lfc/debugger/platform/dhtml/LzDebug.js
===================================================================
--- openlaszlo/trunk/WEB-INF/lps/lfc/debugger/platform/dhtml/LzDebug.js
2007-10-16 02:46:17 UTC (rev 6856)
+++ openlaszlo/trunk/WEB-INF/lps/lfc/debugger/platform/dhtml/LzDebug.js
2007-10-16 03:33:27 UTC (rev 6857)
@@ -282,40 +282,13 @@
else if (thing instanceof String) {
// handled above, but don't fall into array
}
- // Print arrays (actually, anything with a numeric length
- // property) in abbreviated format (what about arrays that have
- // non-numeric props?)
- else if ((typeof(thing.length) == 'number')) {
- // No pretty for subclasses or non-arrays
- if (thing.constructor !== Array) {
- pretty = (! unique);
- }
- var ellip = true;
- var tl = thing.length
- // Don't accumulate beyond limit
- for (var e = 0; (e < tl) && (s.length < limit); e++) {
- // skip non-existent elements
- if ((typeof(thing[e]) == 'undefined')) {
- if (ellip) {
- s += '..., ';
- ellip = false;
- }
- } else {
- ellip = true;
- // TODO: [2005-06-22 ptw] Use __String in case element is huge
- s += String(thing[e]) + ', ';
- }
- }
- if (s != '')
- s = s.substring(0, s.length - 2);
- s = '[' + s + ']';
- }
// If it has a user-defined toString method, use that, but defend
// against broken methods
// TODO [2006-04-11 ptw] try/catch around user method calls
- else if ((thing['toString']) &&
- (this.toString instanceof Function) &&
+ else if (('toString' in thing) &&
+ (thing.toString instanceof Function) &&
(thing.toString !== Object.prototype.toString) &&
+ (thing.toString !== Array.prototype.toString) &&
(typeof(thing.toString()) != 'undefined') &&
(thing.toString() != 'undefined')) {
// No pretty for these, you don't know if the user toString is
@@ -323,42 +296,68 @@
pretty = (! unique);
s = String(thing);
}
- // Print unidentified objects as abbreviated list of props
+ // Print unidentified objects and arrays as abbreviated list of props
else {
- // No pretty for subclasses or non-objects
- if ((! thing instanceof Object) || (thing.constructor !== Object)) {
+ var names = [];
+ // Treat any object with a non-negative integer length as an array
+ var indices = (('length' in thing) &&
+ (Math.floor(thing.length) === thing.length) &&
+ (thing.length >= 0)) ? [] : null;
+
+ this.objectOwnProperties(thing, names, indices, limit);
+ if (indices) { indices.sort(); }
+
+ // No pretty for subclasses or non-objects or array-like objects
+ // that are not Arrays.
+ if (! (indices ?
+ ((thing instanceof Array) && (thing.constructor === Array)) :
+ ((thing instanceof Object) && (thing.constructor === Object))))
{
pretty = (! unique);
}
- var ellip = true;
- for (var e in thing) {
- var v = thing[e];
- var tv = typeof(v);
- var dtv = this.__typeof(v);
- // Don't enumerate inherited props, unless you can't tell
- // (i.e., __proto__ chain is broken
- // Ignore "empty" properties and methods, ignore internal
- // slots and slots that have an internal type
- if (((! (thing instanceof Object)) || thing.hasOwnProperty(e)) &&
- (tv != 'undefined') &&
- (tv != 'function') &&
- (('' + v) != '') &&
- (! this.internalProperty(e)) &&
- (! this.internalProperty(dtv))) {
- ellip = true;
- // TODO: [2005-06-22 ptw] Use __String in case element is huge
- s += '' + e + ': ' + String(v) + ', ';
- } else {
- if (ellip) {
+ if (indices) {
+ // Present as an array, Don't accumulate beyond limit
+ var next = 0;
+ for (var i = 0; (i < indices.length) && (s.length < limit); i ++) {
+ var key = indices[i];
+ if (key != next) {
s += '..., ';
- ellip = false;
}
+ // TODO: [2005-06-22 ptw] Use __String in case element is huge
+ s += String(thing[key]) + ', ';
+ next = key + 1;
}
- // Don't accumulate beyond limit
- if (s.length > limit) break;
+ if (s != '')
+ s = s.substring(0, s.length - 2);
+ s = '[' + s + ']';
+ } else {
+ var ellip = true;
+ // Present as an object, Don't accumulate beyond limit
+ for (var i = 0; (i < names.length) && (s.length < limit); i ++) {
+ var e = names[i];
+ var v = thing[e];
+ var tv = typeof(v);
+ var dtv = this.__typeof(v);
+ // Ignore "empty" properties and methods, ignore internal
+ // slots and slots that have an internal type
+ if ((tv != 'undefined') &&
+ (tv != 'function') &&
+ (('' + v) != '') &&
+ (! this.internalProperty(e)) &&
+ (! this.internalProperty(dtv))) {
+ ellip = true;
+ // TODO: [2005-06-22 ptw] Use __String in case element is huge
+ s += '' + e + ': ' + String(v) + ', ';
+ } else {
+ if (ellip) {
+ s += '..., ';
+ ellip = false;
+ }
+ }
+ }
+ if (s != '')
+ s = s.substring(0, s.length - 2);
+ s = '{' + s + '}';
}
- if (s != '')
- s = s.substring(0, s.length - 2);
- s = '{' + s + '}';
}
} else {
// Shouldn't ever get here
@@ -445,8 +444,12 @@
// Print properties with abbreviated length
this.printLength = this.inspect.printLength;
- var keys = [];
- var arraylen = typeof(obj.length) == 'number' ? obj.length : null;
+ var names = [];
+ // Treat any object with a non-negative integer length as an array
+ var indices = (('length' in obj) &&
+ (Math.floor(obj.length) === obj.length) &&
+ (obj.length >= 0)) ? [] : null;
+
if (si) {
// print unenumerable properties of ECMA objects
// TODO: [2006-04-11 ptw] enumerate Global/Number/Math/Regexp
@@ -454,78 +457,59 @@
for (var p in {callee: true, length: true, constructor: true, prototype:
true}) {
try {
if (hasProto && obj.hasOwnProperty(p)) {
- keys.push(p);
+ names.push(p);
}
} catch (e) {};
}
}
- for (var key in obj) {
- // Print only local slots
- try {
- if ((! hasProto) ||
- obj.hasOwnProperty(key) ||
- // or getter slots (this is a heuristic -- there is no way to
- // ask if a property is a getter)
- (function () { try { return obj[key] } catch (e) {} })() !==
- (function () { try { return obj.constructor.prototype[key] } catch
(e) {} })()
- ) {
- // Print array slots later, in order
- if (arraylen && (key >= 0) && (key < arraylen)) {
- } else if (si ||
- ((! this.internalProperty(key)) &&
- // Only show slots with internal type if showing
- // internals
- (! this.internalProperty(this.__typeof(obj[key]))))) {
- keys.push(key);
- }
- }
- } catch (e) {};
- }
- keys.sort(function (a, b) {
- var al = a.toLowerCase();
- var bl = b.toLowerCase();
- return (al > bl) - (al < bl);
- });
+ this.objectOwnProperties(obj, names, indices);
+
+ names.sort(function (a, b) {
+ var al = a.toLowerCase();
+ var bl = b.toLowerCase();
+ return (al > bl) - (al < bl);
+ });
+ if (indices) { indices.sort(); }
var description = "";
- var kl = keys.length;
+ var nnames = names.length;
var val;
var wid = 0;
- // Align all keys if annotating 'weight'
+ // Align all names if annotating 'weight'
if (this.markGeneration > 0) {
- for (var i = 0; i < kl; i++) {
- var kil = keys[i].length;
- if (kil > wid) { wid = kil; }
+ for (var i = 0; i < nnames; i++) {
+ var keywidth = names[i].length;
+ if (keywidth > wid) { wid = keywidth; }
}
}
- if (arraylen) {
- var kil = ('' + arraylen).length;
- if (kil > wid) { wid = kil; }
+ if (indices) {
+ var keywidth = ('' + obj.length).length;
+ if (keywidth > wid) { wid = keywidth; }
}
var last;
- for (var i = 0; i < kl; i++) {
- var key = keys[i];
+ for (var i = 0; i < nnames; i++) {
+ var key = names[i];
// Some runtimes duplicate inherited slots
if (key != last) {
last = key;
val = obj[key];
- description += ' ' + this.computeSlotDescription(obj, key, val, wid)
+ '\n';
+ if (si ||
+ ((! this.internalProperty(key)) &&
+ // Only show slots with internal type if showing
+ // internals
+ (! this.internalProperty(this.__typeof(val))))) {
+ description += ' ' + this.computeSlotDescription(obj, key,
val, wid) + '\n';
+ }
}
}
- if (arraylen &&
- // Don't print the characters of a string
- (! ((typeof obj == 'string') || (obj instanceof String)))) {
- for (var key = 0; key < arraylen; key++) {
- // Skip non-existent elements, but don't bother with ellipses,
- // since we are displaying the key here
- if ((! hasProto) ||
- obj.hasOwnProperty(key)) {
- val = obj[key];
- if(typeof(val) != 'undefined') {
- description += ' ' + this.computeSlotDescription(obj, key, val,
wid) + '\n';
- }
- }
+ if (indices) {
+ for (var i = 0; i < indices.length; i++) {
+ var key = indices[i];
+ val = obj[key];
+ // Don't bother with ellipses, since we are displaying the key
+ // here
+ description += ' ' + this.computeSlotDescription(obj, key, val, wid)
+ '\n';
}
}
} finally {
Modified: openlaszlo/trunk/WEB-INF/lps/lfc/debugger/platform/swf/LzDebug.as
===================================================================
--- openlaszlo/trunk/WEB-INF/lps/lfc/debugger/platform/swf/LzDebug.as
2007-10-16 02:46:17 UTC (rev 6856)
+++ openlaszlo/trunk/WEB-INF/lps/lfc/debugger/platform/swf/LzDebug.as
2007-10-16 03:33:27 UTC (rev 6857)
@@ -225,12 +225,12 @@
}
}
return null;
-}
+};
/**
* @access private
*/
- Debug.__String = function (thing, pretty, limit, unique) {
+Debug.__String = function (thing, pretty, limit, unique) {
switch (arguments.length) {
case 1:
pretty = this.printPretty;
@@ -343,38 +343,12 @@
else if (thing instanceof String) {
// handled above, but don't fall into array
}
- // Print arrays (actually, anything with a numeric length
- // property) in abbreviated format (what about arrays that have
- // non-numeric props?)
- else if (typeof(thing.length) == 'number') {
- // No pretty for subclasses or non-arrays
- if (op !== Array.prototype) {
- pretty = (! unique);
- }
- var ellip = true;
- var tl = thing.length
- // Don't accumulate beyond limit
- for (var e = 0; (e < tl) && (s.length < limit); e++) {
- // skip non-existent elements
- if (typeof(thing[e]) == 'undefined') {
- if (ellip) {
- s += '..., ';
- ellip = false;
- }
- } else {
- ellip = true;
- // TODO: [2005-06-22 ptw] Use __String in case element is huge
- s += String(thing[e]) + ', ';
- }
- }
- if (s != '')
- s = s.substring(0, s.length - 2);
- s = '[' + s + ']';
- }
// If it has a user-defined toString method, use that, but defend
// against broken methods
- else if ((thing['toString'] instanceof Function) &&
+ else if (('toString' in thing) &&
+ (thing.toString instanceof Function) &&
(thing.toString !== Object.prototype.toString) &&
+ (thing.toString !== Array.prototype.toString) &&
(typeof(thing.toString()) != 'undefined') &&
(thing.toString() != 'undefined')) {
// No pretty for these, you don't know if the user toString is
@@ -382,42 +356,68 @@
pretty = (! unique);
s = String(thing);
}
- // Print unidentified objects as abbreviated list of props
+ // Print unidentified objects and arrays as abbreviated list of props
else {
- // No pretty for subclasses or non-objects
- if (op !== Object.prototype) {
+ var names = [];
+ // Treat any object with a non-negative integer length as an array
+ var indices = (('length' in thing) &&
+ (Math.floor(thing.length) === thing.length) &&
+ (thing.length >= 0)) ? [] : null;
+
+ this.objectOwnProperties(thing, names, indices, limit);
+ if (indices) { indices.sort(); }
+
+ // No pretty for subclasses or non-objects or array-like objects
+ // that are not Arrays.
+ if (! (indices ?
+ ((thing instanceof Array) && (thing.constructor === Array)) :
+ ((thing instanceof Object) && (thing.constructor === Object))))
{
pretty = (! unique);
}
- var ellip = true;
- for (var e in thing) {
- var v = thing[e];
- var tv = typeof(v);
- var dtv = this.__typeof(v);
- // Don't enumerate inherited props, unless you can't tell
- // (i.e., __proto__ chain is broken
- // Ignore "empty" properties and methods, ignore internal
- // slots and slots that have an internal type
- if (((! (thing instanceof Object)) || thing.hasOwnProperty(e)) &&
- (tv != 'undefined') &&
- (tv != 'function') &&
- (('' + v) != '') &&
- (! this.internalProperty(e)) &&
- (! this.internalProperty(dtv))) {
- ellip = true;
- // TODO: [2005-06-22 ptw] Use __String in case element is huge
- s += '' + e + ': ' + String(v) + ', ';
- } else {
- if (ellip) {
+ if (indices) {
+ // Present as an array, Don't accumulate beyond limit
+ var next = 0;
+ for (var i = 0; (i < indices.length) && (s.length < limit); i ++) {
+ var key = indices[i];
+ if (key != next) {
s += '..., ';
- ellip = false;
}
+ // TODO: [2005-06-22 ptw] Use __String in case element is huge
+ s += String(thing[key]) + ', ';
+ next = key + 1;
}
- // Don't accumulate beyond limit
- if (s.length > limit) break;
+ if (s != '')
+ s = s.substring(0, s.length - 2);
+ s = '[' + s + ']';
+ } else {
+ var ellip = true;
+ // Present as an object, Don't accumulate beyond limit
+ for (var i = 0; (i < names.length) && (s.length < limit); i ++) {
+ var e = names[i];
+ var v = thing[e];
+ var tv = typeof(v);
+ var dtv = this.__typeof(v);
+ // Ignore "empty" properties and methods, ignore internal
+ // slots and slots that have an internal type
+ if ((tv != 'undefined') &&
+ (tv != 'function') &&
+ (('' + v) != '') &&
+ (! this.internalProperty(e)) &&
+ (! this.internalProperty(dtv))) {
+ ellip = true;
+ // TODO: [2005-06-22 ptw] Use __String in case element is huge
+ s += '' + e + ': ' + String(v) + ', ';
+ } else {
+ if (ellip) {
+ s += '..., ';
+ ellip = false;
+ }
+ }
+ }
+ if (s != '')
+ s = s.substring(0, s.length - 2);
+ s = '{' + s + '}';
}
- if (s != '')
- s = s.substring(0, s.length - 2);
- s = '{' + s + '}';
}
} else {
// Shouldn't ever get here
@@ -511,42 +511,25 @@
ASSetPropFlags(obj, null, 0, 1);
}
- var keys = [];
- var arraylen = typeof(obj.length) == 'number' ? obj.length : null;
+ var names = [];
+ // Treat any object with a non-negative integer length as an array
+ var indices = (('length' in obj) &&
+ (Math.floor(obj.length) === obj.length) &&
+ (obj.length >= 0)) ? [] : null;
+
if (si) {
// print 'invisible' properties of MovieClip's
if (obj instanceof MovieClip) {
for (var p in {_x: 0, _y: 0, _visible: true, _xscale: 100,
_yscale: 100, _opacity: 100, _rotation: 0,
_currentframe: 1}) {
- keys.push(p);
+ names.push(p);
}
}
}
- for (var key in obj) {
- // Print only local slots
- if ((! hasProto) ||
- obj.hasOwnProperty(key) ||
- // attached movie clips don't show up as 'hasOwnProperty' (but
- // hasOwnProperty is more accurate -- consider if an instance
- // copies a prototype property)
- (obj[key] !== obj.__proto__[key]) ||
- // or getter slots (this is a heuristic -- there is no way to
- // ask if a property is a getter)
- (obj.__proto__.hasOwnProperty(key) &&
- (typeof(obj.__proto__[key]) == 'undefined'))
- ) {
- // Print array slots later, in order
- if (arraylen && (key >= 0) && (key < arraylen)) {
- } else if (si ||
- ((! this.internalProperty(key)) &&
- // Only show slots with internal type if showing
- // internals
- (! this.internalProperty(this.__typeof(obj[key]))))) {
- keys.push(key);
- }
- }
- }
+
+ this.objectOwnProperties(obj, names, indices);
+
if (si) {
// Reset the enumerability
// Make everything unenumerable, and then expose your saved list
@@ -554,42 +537,53 @@
ASSetPropFlags(obj, enumerableSlots, 0, 1);
}
- keys.sort(function (a, b) {
+ names.sort(function (a, b) {
var al = a.toLowerCase();
var bl = b.toLowerCase();
return (al > bl) - (al < bl);
});
+ if (indices) { indices.sort(); }
var description = "";
- var kl = keys.length;
+ var nnames = names.length;
var val;
var wid = 0;
- // Align all keys if annotating 'weight'
+ // Align all names if annotating 'weight'
if (this.markGeneration > 0) {
- for (var i = 0; i < kl; i++) {
- var kil = keys[i].length;
- if (kil > wid) { wid = kil; }
+ for (var i = 0; i < nnames; i++) {
+ var keywidth = names[i].length;
+ if (keywidth > wid) { wid = keywidth; }
}
}
- if (arraylen) {
- var kil = ('' + arraylen).length;
- if (kil > wid) { wid = kil; }
+ if (indices) {
+ var keywidth = ('' + obj.length).length;
+ if (keywidth > wid) { wid = keywidth; }
}
// indent
wid = (wid + 2)
- for (var i = 0; i < kl; i++) {
- var key = keys[i];
- val = obj[key];
- description += this.computeSlotDescription(obj, key, val, wid);
+ var last;
+ for (var i = 0; i < nnames; i++) {
+ var key = names[i];
+ // Some runtimes duplicate inherited slots
+ if (key != last) {
+ last = key;
+ val = obj[key];
+ if (si ||
+ ((! this.internalProperty(key)) &&
+ // Only show slots with internal type if showing
+ // internals
+ (! this.internalProperty(this.__typeof(val))))) {
+ description += this.computeSlotDescription(obj, key, val, wid);
+ }
+ }
}
- if (arraylen) {
- for (var key = 0; key < arraylen; key++) {
+ if (indices) {
+ for (var i = 0; i < indices.length; i++) {
+ var key = indices[i];
val = obj[key];
- // Skip non-existent elements, but don't bother with ellipses,
- // since we are displaying the key here
- if (typeof(val) != 'undefined') {
- description += this.computeSlotDescription(obj, key, val, wid);
- }
+ // Don't bother with ellipses, since we are displaying the key
+ // here
+ description += this.computeSlotDescription(obj, key, val, wid);
}
}
_______________________________________________
Laszlo-checkins mailing list
[email protected]
http://www.openlaszlo.org/mailman/listinfo/laszlo-checkins