Skip to content

Commit 4b01278

Browse files
committed
feat: add maxContainerLength setting to limit length of container types
This is a vscode setting that stores max length of shown elements in container types: list, array, hash table, bitmapset, etc... But this does not prevent getting elements - just stop iteration after reaching this limit, so default value is set to 128 - too much for human to think about.
1 parent 4f98e51 commit 4b01278

File tree

4 files changed

+75
-32
lines changed

4 files changed

+75
-32
lines changed

docs/configuration.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Extension has multiple settings to customize different aspects.
44

55
## VS Code settings
66

7-
There are 3 settings:
7+
There are 4 settings:
88

99
- `postgresql-hacker-helper.logLevel` - minimum log level (for old VS Code up to 1.74.0).
1010

@@ -25,6 +25,13 @@ There are 3 settings:
2525
- If not specified, it will be searched in `*srcPath*/src/tools` directory.
2626
- If specified, and failed to run extension will try to build it.
2727

28+
- `postgresql-hacker-helper.maxContainerLength` - max length of elements shown in container types: `List`, arrays, `Bitmapset`, hash tables, etc...
29+
30+
How many elements must be shown in elements of container type.
31+
This setting must prevent using garbage stored in fields.
32+
33+
Default: `128`
34+
2835
## Configuration file
2936

3037
Extension has config file with custom settings - `.vscode/pgsql_hacker_helper.json`.

package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,13 @@
209209
"title": "Path to root of PostgreSQL source files",
210210
"type": "string",
211211
"description": "Path to source files of PostgreSQL. Set it if you have sources in separate sub-directory from root of project.\nIf not set search will start in project root directory"
212+
},
213+
"postgresql-hacker-helper.maxContainerLength": {
214+
"title": "Max length to show in container types",
215+
"type": "integer",
216+
"default": 128,
217+
"minimum": 1,
218+
"description": "Max length of elements to show in PG variables view. Applies to container types: List, arrays, Bitmapset, HTAB, simplehash, etc..."
212219
}
213220
}
214221
},

src/configuration.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,7 @@ export class VsCodeSettings {
778778
LogLevel: 'logLevel',
779779
PgbsdindentPath: 'pg_bsd_indentPath',
780780
SrcPath: 'srcPath',
781+
MaxContainerLength: 'maxContainerLength',
781782
};
782783

783784
static logLevel: string | undefined;
@@ -801,6 +802,12 @@ export class VsCodeSettings {
801802
return this.srcPath ??= this.getConfig<string>(this.ConfigSections.SrcPath);
802803
}
803804

805+
static maxContainerLength: number | undefined;
806+
static getMaxContainerLength() {
807+
/* 128 - default value specified in package.json */
808+
return this.maxContainerLength ??= (this.getConfig<number>(this.ConfigSections.MaxContainerLength) ?? 128);
809+
}
810+
804811
static getConfig<T>(section: string) {
805812
const topLevelSection = this.ConfigSections.TopLevelSection;
806813
const config = vscode.workspace.getConfiguration(topLevelSection);
@@ -816,6 +823,7 @@ export class VsCodeSettings {
816823
this.srcPath = this.getConfig<string>(this.ConfigSections.SrcPath);
817824
this.customPgBsdIndentPath = this.getConfig<string>(this.ConfigSections.PgbsdindentPath);
818825
this.customNodeTagFiles = this.getConfig<string[]>(this.ConfigSections.NodeTagFiles);
826+
this.maxContainerLength = this.getConfig<number>(this.ConfigSections.MaxContainerLength);
819827
}
820828
}
821829

src/variables.ts

Lines changed: 52 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as constants from './constants';
44
import * as dbg from './debugger';
55
import { Log as logger } from './logger';
66
import { PghhError, EvaluationError, unnullify } from './error';
7-
import { Configuration } from './configuration';
7+
import { Configuration, VsCodeSettings } from './configuration';
88

99
export interface AliasInfo {
1010
/* Declared type */
@@ -579,6 +579,15 @@ export class ExecContext {
579579
}
580580
}
581581

582+
function clampContainerLength(size: number) {
583+
const max = VsCodeSettings.getMaxContainerLength();
584+
return max < size ? max : size;
585+
}
586+
587+
function getMaxContainerLength() {
588+
return VsCodeSettings.getMaxContainerLength();
589+
}
590+
582591
/**
583592
* Special value for frameId used by ephemeral variables:
584593
* they do not need to evaluate anything.
@@ -3897,8 +3906,8 @@ export class ListNodeVariable extends NodeVariable {
38973906
logger.warn('failed to obtain list size for %s', this.name);
38983907
return;
38993908
}
3900-
/* TODO: add size check */
3901-
return length;
3909+
3910+
return clampContainerLength(length);
39023911
}
39033912

39043913
async getListElements() {
@@ -3921,12 +3930,6 @@ export class ListNodeVariable extends NodeVariable {
39213930

39223931

39233932
export class ArraySpecialMember extends RealVariable {
3924-
/**
3925-
* Prevent errors/bugs if there was garbage after
3926-
* length expression evaluation.
3927-
*/
3928-
static plausibleMaxLength = 1024;
3929-
39303933
/**
39313934
* Expression to evaluate to obtain array length.
39323935
* Appended to target struct from right.
@@ -4000,9 +4003,7 @@ export class ArraySpecialMember extends RealVariable {
40004003
}
40014004

40024005
/* Yes, we may have garbage, but what if the array is that huge? */
4003-
if (ArraySpecialMember.plausibleMaxLength < length) {
4004-
length = ArraySpecialMember.plausibleMaxLength;
4005-
}
4006+
length = clampContainerLength(length);
40064007

40074008
const parent = unnullify(this.parent, 'this.parent');
40084009
const memberExpr = `((${parent.type})${parent.getPointer()})->${this.info.memberName}`;
@@ -4063,11 +4064,14 @@ class BitmapSetSpecialMember extends NodeVariable {
40634064
}
40644065

40654066
const n = Number(nwords.value);
4067+
40664068
/*
4067-
* For 64-bit system even 20 is large number: 10 * 64 = 640.
4068-
* Human will not be able to handle such amount of data.
4069+
* For 64-bit system even 50 is large number: 50 * 64 = 3200.
4070+
* But actually this is *potential* size, so can we allow such
4071+
* large value - in reality it can contain only 1 element, just
4072+
* in last word, but this acts like a pretty good correctness check.
40694073
*/
4070-
const maxNWords = 10;
4074+
const maxNWords = 50;
40714075
if (Number.isNaN(n) || maxNWords <= n) {
40724076
return false;
40734077
}
@@ -4165,6 +4169,7 @@ class BitmapSetSpecialMember extends NodeVariable {
41654169

41664170
let number = -1;
41674171
const numbers = [];
4172+
const maxLength = getMaxContainerLength();
41684173
do {
41694174
const expression = `bms_next_member((Bitmapset *)${this.getPointer()}, ${number})`;
41704175
try {
@@ -4188,7 +4193,7 @@ class BitmapSetSpecialMember extends NodeVariable {
41884193
}
41894194

41904195
numbers.push(number);
4191-
} while (number >= 0);
4196+
} while (number >= 0 && numbers.length < maxLength);
41924197

41934198
return numbers;
41944199
}
@@ -4216,6 +4221,7 @@ class BitmapSetSpecialMember extends NodeVariable {
42164221
}
42174222

42184223
const expression = `bms_first_member((Bitmapset *)${e.result})`;
4224+
const maxLength = getMaxContainerLength();
42194225
let number = -1;
42204226
const numbers = [];
42214227
do {
@@ -4232,7 +4238,7 @@ class BitmapSetSpecialMember extends NodeVariable {
42324238
}
42334239

42344240
numbers.push(number);
4235-
} while (number >= 0);
4241+
} while (number >= 0 && numbers.length < maxLength);
42364242

42374243
await this.pfree(e.result);
42384244

@@ -4843,7 +4849,6 @@ class HTABElementsMember extends Variable {
48434849
*/
48444850
try {
48454851
await this.evaluateVoid(`hash_seq_term((HASH_SEQ_STATUS *)${hashSeqStatus})`);
4846-
48474852
} catch (err) {
48484853
if (!(err instanceof EvaluationError)) {
48494854
throw err;
@@ -4879,6 +4884,7 @@ class HTABElementsMember extends Variable {
48794884
return;
48804885
}
48814886

4887+
const maxLength = getMaxContainerLength();
48824888
let entry;
48834889
while ((entry = await this.getNextHashEntry(hashSeqStatus))) {
48844890
let result;
@@ -4897,13 +4903,14 @@ class HTABElementsMember extends Variable {
48974903
return undefined;
48984904
}
48994905

4906+
let variable;
49004907
try {
4901-
const variable = await Variable.create({
4908+
variable = await Variable.create({
49024909
...result,
4903-
name: `${variables.length}`,
4910+
name: `[${variables.length}]`,
49044911
value: result.result,
4912+
memoryReference: result.memoryReference,
49054913
}, this.frameId, this.context, this);
4906-
variables.push(variable);
49074914
} catch (error) {
49084915
if (error instanceof EvaluationError) {
49094916
await this.finalizeHashSeqStatus(hashSeqStatus);
@@ -4912,6 +4919,16 @@ class HTABElementsMember extends Variable {
49124919

49134920
throw error;
49144921
}
4922+
4923+
variables.push(variable);
4924+
if (maxLength < variables.length) {
4925+
/*
4926+
* If we terminate iteration before iteration is completed,
4927+
* we have to call finalizer function
4928+
*/
4929+
await this.finalizeHashSeqStatus(hashSeqStatus);
4930+
break;
4931+
}
49154932
}
49164933

49174934
await this.pfree(hashSeqStatus);
@@ -5008,15 +5025,16 @@ class SimplehashElementsMember extends Variable {
50085025
hashTable);
50095026
this.hashTable = hashTable;
50105027
}
5028+
5029+
protected async getDescription() {
5030+
return '';
5031+
}
50115032

5012-
async getTreeItem(): Promise<vscode.TreeItem> {
5013-
/* Show only '$elements$' */
5014-
return {
5015-
label: '$elements$',
5016-
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
5017-
};
5033+
protected isExpandable() {
5034+
return true;
50185035
}
50195036

5037+
/* TODO: no need to cache these */
50205038
/*
50215039
* Cached identifier names for function and types
50225040
*/
@@ -5087,7 +5105,7 @@ class SimplehashElementsMember extends Variable {
50875105
return iteratorPtr;
50885106
}
50895107

5090-
async iterate(iterator: string, current: number) {
5108+
async iterate(iterator: string, index: number) {
50915109
const iterFunction = this.getIteratorFunction();
50925110
const hashTableType = `(${this.getHashTableType()} *) ${this.hashTable.getPointer()}`;
50935111
const iteratorArg = `(${this.getIteratorType()} *) ${iterator}`;
@@ -5113,8 +5131,9 @@ class SimplehashElementsMember extends Variable {
51135131
try {
51145132
return await Variable.create({
51155133
...result,
5116-
name: `${current}`,
5134+
name: `[${index}]`,
51175135
value: result.result,
5136+
memoryReference: result.memoryReference,
51185137
}, this.frameId, this.context, this);
51195138
} catch (err) {
51205139
if (!(err instanceof EvaluationError)) {
@@ -5149,10 +5168,12 @@ class SimplehashElementsMember extends Variable {
51495168
return;
51505169
}
51515170

5171+
const maxLength = getMaxContainerLength();
51525172
const variables = [];
51535173
let id = 0;
51545174
let variable;
5155-
while ((variable = await this.iterate(iterator, id))) {
5175+
while ( variables.length < maxLength
5176+
&& (variable = await this.iterate(iterator, id))) {
51565177
++id;
51575178
variables.push(variable);
51585179
}
@@ -5179,7 +5200,7 @@ class FlagsMemberVariable extends RealVariable {
51795200
* debug symbols (i.e. for gdb use '-g3' level).
51805201
*
51815202
* But even if we know numeric value of enum member and have the same
5182-
* endianess we still have to evaluate all expressions in debugger, because
5203+
* endianness we still have to evaluate all expressions in debugger, because
51835204
* 1) due to another major pg version numeric values can change
51845205
* 2) we can debug coredump collected from another machine, so we should not
51855206
* assume this PC is binary compatible with one that is debugged

0 commit comments

Comments
 (0)