Skip to content
This repository was archived by the owner on Apr 3, 2024. It is now read-only.

Commit 53562cc

Browse files
Update truncated object message (#269)
This change additionally avoids truncating evaluated expressions and makes behavior more consistent with other language debug agents. PR-URL: #269
1 parent 7e2bc4f commit 53562cc

File tree

2 files changed

+141
-39
lines changed

2 files changed

+141
-39
lines changed

src/agent/state.js

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ var BUFFER_FULL_MESSAGE_INDEX = 0;
3636
var NATIVE_PROPERTY_MESSAGE_INDEX = 1;
3737
var GETTER_MESSAGE_INDEX = 2;
3838
var ARG_LOCAL_LIMIT_MESSAGE_INDEX = 3;
39-
var STRING_LIMIT_MESSAGE_INDEX = 4;
4039

4140
/**
4241
* Captures the stack and current execution state.
@@ -115,12 +114,6 @@ function StateResolver(execState, expressions, config, v8) {
115114
config.capture.maxExpandFrames +
116115
'` stack frames.',
117116
true) };
118-
this.messageTable_[STRING_LIMIT_MESSAGE_INDEX] =
119-
{ status: new StatusMessage(StatusMessage.VARIABLE_VALUE,
120-
'Only first `config.capture.maxStringLength=' +
121-
config.capture.maxStringLength +
122-
'` chars were captured.',
123-
false) };
124117

125118
this.resolvedVariableTable_ = util._extend([], this.messageTable_);
126119
this.rawVariableTable_ = this.messageTable_.map(function() { return null; });
@@ -137,6 +130,7 @@ StateResolver.prototype.capture_ = function() {
137130
var that = this;
138131

139132
// Evaluate the watch expressions
133+
var evalIndexSet = new Set();
140134
if (that.expressions_) {
141135
that.expressions_.forEach(function(expression, index) {
142136
var result = evaluate(expression, that.state_.frame(0));
@@ -149,7 +143,11 @@ StateResolver.prototype.capture_ = function() {
149143
result.error, true)
150144
};
151145
} else {
152-
evaluated = that.resolveVariable_(expression, result.mirror);
146+
evaluated = that.resolveVariable_(expression, result.mirror, true);
147+
var varTableIdx = evaluated.varTableIndex;
148+
if (typeof varTableIdx !== 'undefined') {
149+
evalIndexSet.add(varTableIdx);
150+
}
153151
}
154152
that.evaluatedExpressions_[index] = evaluated;
155153
});
@@ -166,8 +164,9 @@ StateResolver.prototype.capture_ = function() {
166164
while (index < that.rawVariableTable_.length && // NOTE: length changes in loop
167165
(that.totalSize_ < that.config_.capture.maxDataSize || noLimit)) {
168166
assert(!that.resolvedVariableTable_[index]); // shouldn't have it resolved yet
167+
var isEvaluated = evalIndexSet.has(index);
169168
that.resolvedVariableTable_[index] =
170-
that.resolveMirror_(that.rawVariableTable_[index]);
169+
that.resolveMirror_(that.rawVariableTable_[index], isEvaluated);
171170
index++;
172171
}
173172

@@ -380,7 +379,7 @@ StateResolver.prototype.resolveLocalsList_ = function (frame, args) {
380379
// It's a valid variable that belongs in the locals list and wasn't
381380
// discovered at a lower-scope
382381
usedNames[name] = true;
383-
locals.push(self.resolveVariable_(name, trg));
382+
locals.push(self.resolveVariable_(name, trg, false));
384383
} // otherwise another same-named variable occured at a lower scope
385384
return locals;
386385
},
@@ -394,7 +393,7 @@ StateResolver.prototype.resolveLocalsList_ = function (frame, args) {
394393
// under the name 'context' which is used by the Chrome DevTools.
395394
var ctx = frame.details().receiver();
396395
if (ctx) {
397-
return [self.resolveVariable_('context', makeMirror(ctx))];
396+
return [self.resolveVariable_('context', makeMirror(ctx), false)];
398397
}
399398
return [];
400399
}()));
@@ -407,8 +406,10 @@ StateResolver.prototype.resolveLocalsList_ = function (frame, args) {
407406
*
408407
* @param {String} name The name of the variable.
409408
* @param {Object} value A v8 debugger representation of a variable value.
409+
* @param {boolean} isEvaluated Specifies if the variable is from a watched
410+
* expression.
410411
*/
411-
StateResolver.prototype.resolveVariable_ = function(name, value) {
412+
StateResolver.prototype.resolveVariable_ = function(name, value, isEvaluated) {
412413
var size = name.length;
413414

414415
var data = {
@@ -419,9 +420,13 @@ StateResolver.prototype.resolveVariable_ = function(name, value) {
419420
// primitives: undefined, null, boolean, number, string, symbol
420421
data.value = value.toText();
421422
var maxLength = this.config_.capture.maxStringLength;
422-
if (maxLength && maxLength < data.value.length) {
423+
if (!isEvaluated && maxLength && maxLength < data.value.length) {
424+
data.status = new StatusMessage(StatusMessage.VARIABLE_VALUE,
425+
'Only first `config.capture.maxStringLength=' +
426+
this.config_.capture.maxStringLength +
427+
'` chars were captured for string of length ' + data.value.length +
428+
'. Use in an expression to see the full string.', false);
423429
data.value = data.value.substring(0, maxLength) + '...';
424-
data.status = this.messageTable_[STRING_LIMIT_MESSAGE_INDEX].status;
425430
}
426431

427432
} else if (value.isFunction()) {
@@ -462,26 +467,27 @@ StateResolver.prototype.storeObjectToVariableTable_ = function(obj) {
462467
* Responsible for recursively resolving the properties on a
463468
* provided object mirror.
464469
*/
465-
StateResolver.prototype.resolveMirror_ = function(mirror) {
470+
StateResolver.prototype.resolveMirror_ = function(mirror, isEvaluated) {
466471
var properties = mirror.properties();
467472
var maxProps = this.config_.capture.maxProperties;
468473
var truncate = maxProps && properties.length > maxProps;
469-
if (truncate) {
474+
if (!isEvaluated && truncate) {
470475
properties = properties.slice(0, maxProps);
471476
}
472-
var members = properties.map(this.resolveMirrorProperty_.bind(this));
473-
if (truncate) {
477+
var members = properties.map(this.resolveMirrorProperty_.bind(this, isEvaluated));
478+
if (!isEvaluated && truncate) {
474479
members.push({name: 'Only first `config.capture.maxProperties=' +
475480
this.config_.capture.maxProperties +
476-
'` properties were captured'});
481+
'` properties were captured. Use in an expression' +
482+
' to see all properties.'});
477483
}
478484
return {
479485
value: mirror.toText(),
480486
members: members
481487
};
482488
};
483489

484-
StateResolver.prototype.resolveMirrorProperty_ = function(property) {
490+
StateResolver.prototype.resolveMirrorProperty_ = function(isEvaluated, property) {
485491
var name = String(property.name());
486492
// Array length must be special cased as it is a native property that
487493
// we know to be safe to evaluate which is not generally true.
@@ -498,5 +504,5 @@ StateResolver.prototype.resolveMirrorProperty_ = function(property) {
498504
varTableIndex: GETTER_MESSAGE_INDEX
499505
};
500506
}
501-
return this.resolveVariable_(name, property.value());
507+
return this.resolveVariable_(name, property.value(), isEvaluated);
502508
};

test/test-v8debugapi.js

Lines changed: 114 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -710,8 +710,7 @@ describe('v8debugapi', function() {
710710
it('should limit string length', function(done) {
711711
var bp = {
712712
id: 'fake-id-124',
713-
location: { path: 'test-v8debugapi.js', line: 9 },
714-
expressions: ['hasGetter']
713+
location: { path: 'test-v8debugapi.js', line: 9 }
715714
};
716715
var oldMaxLength = config.capture.maxStringLength;
717716
var oldMaxData = config.capture.maxDataSize;
@@ -721,8 +720,10 @@ describe('v8debugapi', function() {
721720
assert.ifError(err);
722721
api.wait(bp, function(err) {
723722
assert.ifError(err);
724-
var hasGetter = bp.evaluatedExpressions[0];
725-
var getterVal = bp.variableTable[hasGetter.varTableIndex];
723+
var hasGetter = bp.stackFrames[0].locals.filter(function(value) {
724+
return value.name === 'hasGetter';
725+
});
726+
var getterVal = bp.variableTable[hasGetter[0].varTableIndex];
726727
var stringItems = getterVal.members.filter(function(m) {
727728
return m.value === 'hel...';
728729
});
@@ -732,6 +733,7 @@ describe('v8debugapi', function() {
732733
assert(item.status.description.format.indexOf('Only first') !== -1);
733734
assert(item.status.description.format.indexOf(
734735
'config.capture.maxStringLength=3') !== -1);
736+
assert(item.status.description.format.indexOf('of length 11.') !== -1);
735737

736738
api.clear(bp);
737739
config.capture.maxDataSize = oldMaxData;
@@ -745,21 +747,22 @@ describe('v8debugapi', function() {
745747
it('should limit array length', function(done) {
746748
var bp = {
747749
id: 'fake-id-124',
748-
location: { path: 'test-v8debugapi.js', line: 5 },
749-
expressions: ['A']
750+
location: { path: 'test-v8debugapi.js', line: 5 }
750751
};
751752
var oldMax = config.capture.maxProperties;
752753
config.capture.maxProperties = 1;
753754
api.set(bp, function(err) {
754755
assert.ifError(err);
755756
api.wait(bp, function(err) {
756757
assert.ifError(err);
757-
var foo = bp.evaluatedExpressions[0];
758-
var fooVal = bp.variableTable[foo.varTableIndex];
758+
var aResults = bp.stackFrames[0].locals.filter(function(value) {
759+
return value.name === 'A';
760+
});
761+
var aVal = bp.variableTable[aResults[0].varTableIndex];
759762
// should have 1 element + truncation message.
760-
assert.equal(fooVal.members.length, 2);
761-
assert(fooVal.members[1].name.indexOf('Only first') !== -1);
762-
assert(fooVal.members[1].name.indexOf(
763+
assert.equal(aVal.members.length, 2);
764+
assert(aVal.members[1].name.indexOf('Only first') !== -1);
765+
assert(aVal.members[1].name.indexOf(
763766
'config.capture.maxProperties=1') !== -1);
764767

765768
api.clear(bp);
@@ -773,21 +776,22 @@ describe('v8debugapi', function() {
773776
it('should limit object length', function(done) {
774777
var bp = {
775778
id: 'fake-id-124',
776-
location: { path: 'test-v8debugapi.js', line: 5 },
777-
expressions: ['B']
779+
location: { path: 'test-v8debugapi.js', line: 5 }
778780
};
779781
var oldMax = config.capture.maxProperties;
780782
config.capture.maxProperties = 1;
781783
api.set(bp, function(err) {
782784
assert.ifError(err);
783785
api.wait(bp, function(err) {
784786
assert.ifError(err);
785-
var foo = bp.evaluatedExpressions[0];
786-
var fooVal = bp.variableTable[foo.varTableIndex];
787+
var bResults = bp.stackFrames[0].locals.filter(function(value) {
788+
return value.name === 'B';
789+
});
790+
var bVal = bp.variableTable[bResults[0].varTableIndex];
787791
// should have 1 element + truncation message
788-
assert.equal(fooVal.members.length, 2);
789-
assert(fooVal.members[1].name.indexOf('Only first') !== -1);
790-
assert(fooVal.members[1].name.indexOf(
792+
assert.equal(bVal.members.length, 2);
793+
assert(bVal.members[1].name.indexOf('Only first') !== -1);
794+
assert(bVal.members[1].name.indexOf(
791795
'config.capture.maxProperties=1') !== -1);
792796

793797
api.clear(bp);
@@ -798,6 +802,98 @@ describe('v8debugapi', function() {
798802
});
799803
});
800804

805+
it('should not limit the length of an evaluated string based on maxStringLength',
806+
function(done) {
807+
var bp = {
808+
id: 'fake-id-124',
809+
location: { path: 'test-v8debugapi.js', line: 9 },
810+
expressions: ['hasGetter']
811+
};
812+
var oldMaxLength = config.capture.maxStringLength;
813+
var oldMaxData = config.capture.maxDataSize;
814+
config.capture.maxStringLength = 3;
815+
config.capture.maxDataSize = 20000;
816+
api.set(bp, function(err) {
817+
assert.ifError(err);
818+
api.wait(bp, function(err) {
819+
assert.ifError(err);
820+
var hasGetter = bp.evaluatedExpressions[0];
821+
var getterVal = bp.variableTable[hasGetter.varTableIndex];
822+
var stringItems = getterVal.members.filter(function(m) {
823+
return m.value === 'hello world';
824+
});
825+
// The property would have value 'hel...' if truncation occured
826+
// resulting in stringItems.length being 0.
827+
assert(stringItems.length === 1);
828+
829+
api.clear(bp);
830+
config.capture.maxDataSize = oldMaxData;
831+
config.capture.maxStringLength = oldMaxLength;
832+
done();
833+
});
834+
process.nextTick(function() {getterObject();});
835+
});
836+
});
837+
838+
it('should not limit the length of an evaluated array based on maxProperties',
839+
function(done) {
840+
var bp = {
841+
id: 'fake-id-124',
842+
location: { path: 'test-v8debugapi.js', line: 5 },
843+
expressions: ['A']
844+
};
845+
var oldMaxProps = config.capture.maxProperties;
846+
var oldMaxData = config.capture.maxDataSize;
847+
config.capture.maxProperties = 1;
848+
config.capture.maxDataSize = 20000;
849+
api.set(bp, function(err) {
850+
assert.ifError(err);
851+
api.wait(bp, function(err) {
852+
assert.ifError(err);
853+
var foo = bp.evaluatedExpressions[0];
854+
var fooVal = bp.variableTable[foo.varTableIndex];
855+
// '1', '2', '3', and 'length'
856+
assert.equal(fooVal.members.length, 4);
857+
assert.strictEqual(foo.status, undefined);
858+
859+
api.clear(bp);
860+
config.capture.maxDataSize = oldMaxData;
861+
config.capture.maxProperties = oldMaxProps;
862+
done();
863+
});
864+
process.nextTick(function() {foo(2);});
865+
});
866+
});
867+
868+
it('should not limit the length of an evaluated object based on maxProperties',
869+
function(done) {
870+
var bp = {
871+
id: 'fake-id-124',
872+
location: { path: 'test-v8debugapi.js', line: 5 },
873+
expressions: ['B']
874+
};
875+
var oldMaxProps = config.capture.maxProperties;
876+
var oldMaxData = config.capture.maxDataSize;
877+
config.capture.maxProperties = 1;
878+
config.capture.maxDataSize = 20000;
879+
api.set(bp, function(err) {
880+
assert.ifError(err);
881+
api.wait(bp, function(err) {
882+
assert.ifError(err);
883+
var foo = bp.evaluatedExpressions[0];
884+
var fooVal = bp.variableTable[foo.varTableIndex];
885+
assert.equal(fooVal.members.length, 3);
886+
assert.strictEqual(foo.status, undefined);
887+
888+
api.clear(bp);
889+
config.capture.maxDataSize = oldMaxData;
890+
config.capture.maxProperties = oldMaxProps;
891+
done();
892+
});
893+
process.nextTick(function() {foo(2);});
894+
});
895+
});
896+
801897
it('should display an error for an evaluated array beyond maxDataSize',
802898
function(done) {
803899
var bp = {

0 commit comments

Comments
 (0)