Skip to content

Commit e7f8e69

Browse files
authored
Use valibot for symbolication API response validation (#5666)
2 parents c7066b0 + a64c102 commit e7f8e69

File tree

4 files changed

+46
-127
lines changed

4 files changed

+46
-127
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
"redux-thunk": "^3.1.0",
103103
"reselect": "^4.1.8",
104104
"url": "^0.11.4",
105+
"valibot": "^1.1.0",
105106
"weaktuplemap": "^1.0.0",
106107
"workbox-window": "^7.3.0"
107108
},

src/profile-logic/mozilla-symbolication-api.ts

Lines changed: 39 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* This Source Code Form is subject to the terms of the Mozilla Public
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
import * as v from 'valibot';
45
import type {
56
AddressResult,
67
LibSymbolicationRequest,
@@ -28,156 +29,68 @@ export type QuerySymbolicationApiCallback = (
2829
requestJson: string
2930
) => Promise<unknown>;
3031

31-
type APIFoundModulesV5 = {
32-
// For every requested library in the memoryMap, this object contains a string
33-
// key of the form `${debugName}/${breakpadId}`. The value is null if no
34-
// address with the module index was requested, and otherwise a boolean that
35-
// says whether the symbol server had symbols for this library.
36-
[key: string]: null | boolean;
37-
};
32+
// Valibot schemas for API response validation
3833

39-
type APIInlineFrameInfoV5 = {
34+
// For every requested library in the memoryMap, this object contains a string
35+
// key of the form `${debugName}/${breakpadId}`. The value is null if no
36+
// address with the module index was requested, and otherwise a boolean that
37+
// says whether the symbol server had symbols for this library.
38+
const APIFoundModulesV5Schema = v.record(v.string(), v.nullable(v.boolean()));
39+
40+
// Information about functions that were inlined at this address.
41+
const APIInlineFrameInfoV5Schema = v.object({
4042
// The name of the function this inline frame was in, if known.
41-
function?: string;
43+
function: v.optional(v.string()),
4244
// The path of the file that contains the function this inline frame was in, optional.
43-
file?: string;
45+
file: v.optional(v.string()),
4446
// The line number that contains the source code for this inline frame that
4547
// contributed to the instruction at the looked-up address, optional.
4648
// e.g. 543
47-
line?: number;
48-
};
49+
line: v.optional(v.number()),
50+
});
4951

50-
type APIFrameInfoV5 = {
52+
const APIFrameInfoV5Schema = v.object({
5153
// The hex version of the address that we requested (e.g. "0x5ab").
52-
module_offset: string;
54+
module_offset: v.string(),
5355
// The debugName of the library that this frame was in.
54-
module: string;
56+
module: v.string(),
5557
// The index of this APIFrameInfo in its enclosing APIStack.
56-
frame: number;
58+
frame: v.number(),
5759
// The name of the function this frame was in, if symbols were found.
58-
function?: string;
60+
function: v.optional(v.string()),
5961
// The hex offset between the requested address and the start of the function,
6062
// e.g. "0x3c".
61-
function_offset?: string;
63+
function_offset: v.optional(v.string()),
6264
// An optional size, in bytes, of the machine code of the outer function that
6365
// this address belongs to, as a hex string, e.g. "0x270".
64-
function_size?: string;
66+
function_size: v.optional(v.string()),
6567
// The path of the file that contains the function this frame was in, optional.
66-
// As of June 2021, this is only supported on the staging symbolication server
67-
// ("Eliot") but not on the implementation that's currently in production ("Tecken").
68-
// e.g. "hg:hg.mozilla.org/mozilla-central:js/src/vm/Interpreter.cpp:24938c537a55f9db3913072d33b178b210e7d6b5"
69-
file?: string;
68+
file: v.optional(v.string()),
7069
// The line number that contains the source code that generated the instructions at the address, optional.
71-
// (Same support as file.)
72-
// e.g. 543
73-
line?: number;
70+
line: v.optional(v.number()),
7471
// Information about functions that were inlined at this address.
7572
// Ordered from inside to outside.
76-
// As of November 2021, this is only supported by profiler-symbol-server.
77-
// Adding this functionality to the Mozilla symbol server is tracked in
78-
// https://bugzilla.mozilla.org/show_bug.cgi?id=1636194
79-
inlines?: APIInlineFrameInfoV5[];
80-
};
73+
inlines: v.optional(v.array(APIInlineFrameInfoV5Schema)),
74+
});
8175

82-
type APIStackV5 = APIFrameInfoV5[];
76+
const APIStackV5Schema = v.array(APIFrameInfoV5Schema);
8377

84-
type APIJobResultV5 = {
85-
found_modules: APIFoundModulesV5;
86-
stacks: APIStackV5[];
87-
};
78+
const APIJobResultV5Schema = v.object({
79+
found_modules: APIFoundModulesV5Schema,
80+
stacks: v.array(APIStackV5Schema),
81+
});
8882

89-
type APIResultV5 = {
90-
results: APIJobResultV5[];
91-
};
83+
const APIResultV5Schema = v.object({
84+
results: v.array(APIJobResultV5Schema),
85+
});
9286

93-
// Make sure that the JSON blob we receive from the API conforms to our flow
94-
// type definition.
95-
function _ensureIsAPIResultV5(result: unknown): APIResultV5 {
96-
// It's possible (especially when running tests with Jest) that the parameter
97-
// inherits from a `Object` global from another realm. By using toString
98-
// this issue is solved wherever the parameter comes from.
99-
const isObject = (subject: unknown) =>
100-
Object.prototype.toString.call(subject) === '[object Object]';
87+
type APIJobResultV5 = v.InferOutput<typeof APIJobResultV5Schema>;
88+
type APIResultV5 = v.InferOutput<typeof APIResultV5Schema>;
10189

102-
if (!isObject(result) || !('results' in (result as object))) {
103-
throw new Error('Expected an object with property `results`');
104-
}
105-
const results = (result as { results: unknown }).results;
106-
if (!Array.isArray(results)) {
107-
throw new Error('Expected `results` to be an array');
108-
}
109-
for (const jobResult of results) {
110-
if (
111-
!isObject(jobResult) ||
112-
!('found_modules' in jobResult) ||
113-
!('stacks' in jobResult)
114-
) {
115-
throw new Error(
116-
'Expected jobResult to have `found_modules` and `stacks` properties'
117-
);
118-
}
119-
const found_modules = jobResult.found_modules;
120-
if (!isObject(found_modules)) {
121-
throw new Error('Expected `found_modules` to be an object');
122-
}
123-
const stacks = jobResult.stacks;
124-
if (!Array.isArray(stacks)) {
125-
throw new Error('Expected `stacks` to be an array');
126-
}
127-
for (const stack of stacks) {
128-
if (!Array.isArray(stack)) {
129-
throw new Error('Expected `stack` to be an array');
130-
}
131-
for (const frameInfo of stack) {
132-
if (!isObject(frameInfo)) {
133-
throw new Error('Expected `frameInfo` to be an object');
134-
}
135-
if (
136-
!('module_offset' in frameInfo) ||
137-
!('module' in frameInfo) ||
138-
!('frame' in frameInfo)
139-
) {
140-
throw new Error(
141-
'Expected frameInfo to have `module_offset`, `module` and `frame` properties'
142-
);
143-
}
144-
if ('file' in frameInfo && typeof frameInfo.file !== 'string') {
145-
throw new Error('Expected frameInfo.file to be a string, if present');
146-
}
147-
if ('line' in frameInfo && typeof frameInfo.line !== 'number') {
148-
throw new Error('Expected frameInfo.line to be a number, if present');
149-
}
150-
if (
151-
'function_offset' in frameInfo &&
152-
typeof frameInfo.function_offset !== 'string'
153-
) {
154-
throw new Error(
155-
'Expected frameInfo.function_offset to be a string, if present'
156-
);
157-
}
158-
if (
159-
'function_size' in frameInfo &&
160-
typeof frameInfo.function_size !== 'string'
161-
) {
162-
throw new Error(
163-
'Expected frameInfo.function_size to be a string, if present'
164-
);
165-
}
166-
if ('inlines' in frameInfo) {
167-
const inlines = frameInfo.inlines;
168-
if (!Array.isArray(inlines)) {
169-
throw new Error('Expected `inlines` to be an array');
170-
}
171-
for (const inlineFrame of inlines) {
172-
if (!isObject(inlineFrame)) {
173-
throw new Error('Expected `inlineFrame` to be an object');
174-
}
175-
}
176-
}
177-
}
178-
}
179-
}
180-
return result as APIResultV5;
90+
// Make sure that the JSON blob we receive from the API conforms to our
91+
// type definition using valibot validation.
92+
function _ensureIsAPIResultV5(result: unknown): APIResultV5 {
93+
return v.parse(APIResultV5Schema, result);
18194
}
18295

18396
function getV5ResultForLibRequest(

src/test/store/receive-profile.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -937,7 +937,7 @@ describe('actions/receive-profile', function () {
937937
expect.objectContaining({
938938
message:
939939
'Could not obtain symbols for libxul/SOMETHING_FAKE.\n' +
940-
' - Error: There was a problem with the symbolication API request to the symbol server: Expected an object with property `results`\n' +
940+
' - Error: There was a problem with the symbolication API request to the symbol server: Invalid key: Expected "results" but received undefined\n' +
941941
' - Error: No connection to the browser, cannot run querySymbolicationApi\n' +
942942
' - Error: No connection to the browser, cannot obtain symbol tables',
943943
})

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12311,6 +12311,11 @@ v8-to-istanbul@^9.0.1:
1231112311
"@types/istanbul-lib-coverage" "^2.0.1"
1231212312
convert-source-map "^1.6.0"
1231312313

12314+
valibot@^1.1.0:
12315+
version "1.1.0"
12316+
resolved "https://registry.yarnpkg.com/valibot/-/valibot-1.1.0.tgz#873bb1af9e1577391690307bfe0520bd1360ec2d"
12317+
integrity sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==
12318+
1231412319
validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4:
1231512320
version "3.0.4"
1231612321
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"

0 commit comments

Comments
 (0)