|
1 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| 4 | +import * as v from 'valibot'; |
4 | 5 | import type { |
5 | 6 | AddressResult, |
6 | 7 | LibSymbolicationRequest, |
@@ -28,156 +29,68 @@ export type QuerySymbolicationApiCallback = ( |
28 | 29 | requestJson: string |
29 | 30 | ) => Promise<unknown>; |
30 | 31 |
|
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 |
38 | 33 |
|
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({ |
40 | 42 | // The name of the function this inline frame was in, if known. |
41 | | - function?: string; |
| 43 | + function: v.optional(v.string()), |
42 | 44 | // The path of the file that contains the function this inline frame was in, optional. |
43 | | - file?: string; |
| 45 | + file: v.optional(v.string()), |
44 | 46 | // The line number that contains the source code for this inline frame that |
45 | 47 | // contributed to the instruction at the looked-up address, optional. |
46 | 48 | // e.g. 543 |
47 | | - line?: number; |
48 | | -}; |
| 49 | + line: v.optional(v.number()), |
| 50 | +}); |
49 | 51 |
|
50 | | -type APIFrameInfoV5 = { |
| 52 | +const APIFrameInfoV5Schema = v.object({ |
51 | 53 | // The hex version of the address that we requested (e.g. "0x5ab"). |
52 | | - module_offset: string; |
| 54 | + module_offset: v.string(), |
53 | 55 | // The debugName of the library that this frame was in. |
54 | | - module: string; |
| 56 | + module: v.string(), |
55 | 57 | // The index of this APIFrameInfo in its enclosing APIStack. |
56 | | - frame: number; |
| 58 | + frame: v.number(), |
57 | 59 | // The name of the function this frame was in, if symbols were found. |
58 | | - function?: string; |
| 60 | + function: v.optional(v.string()), |
59 | 61 | // The hex offset between the requested address and the start of the function, |
60 | 62 | // e.g. "0x3c". |
61 | | - function_offset?: string; |
| 63 | + function_offset: v.optional(v.string()), |
62 | 64 | // An optional size, in bytes, of the machine code of the outer function that |
63 | 65 | // this address belongs to, as a hex string, e.g. "0x270". |
64 | | - function_size?: string; |
| 66 | + function_size: v.optional(v.string()), |
65 | 67 | // 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()), |
70 | 69 | // 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()), |
74 | 71 | // Information about functions that were inlined at this address. |
75 | 72 | // 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 | +}); |
81 | 75 |
|
82 | | -type APIStackV5 = APIFrameInfoV5[]; |
| 76 | +const APIStackV5Schema = v.array(APIFrameInfoV5Schema); |
83 | 77 |
|
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 | +}); |
88 | 82 |
|
89 | | -type APIResultV5 = { |
90 | | - results: APIJobResultV5[]; |
91 | | -}; |
| 83 | +const APIResultV5Schema = v.object({ |
| 84 | + results: v.array(APIJobResultV5Schema), |
| 85 | +}); |
92 | 86 |
|
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>; |
101 | 89 |
|
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); |
181 | 94 | } |
182 | 95 |
|
183 | 96 | function getV5ResultForLibRequest( |
|
0 commit comments