wasmXScopes.js (5635B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ 4 5 /* eslint camelcase: 0*/ 6 7 "use strict"; 8 9 const { 10 decodeExpr, 11 } = require("resource://devtools/client/shared/source-map-loader/wasm-dwarf/wasmDwarfExpressions"); 12 13 const xScopes = new Map(); 14 15 function indexLinkingNames(items) { 16 const result = new Map(); 17 let queue = [...items]; 18 while (queue.length) { 19 const item = queue.shift(); 20 if ("uid" in item) { 21 result.set(item.uid, item); 22 } else if ("linkage_name" in item) { 23 // TODO the linkage_name string value is used for compatibility 24 // with old format. Remove in favour of the uid referencing. 25 result.set(item.linkage_name, item); 26 } 27 if ("children" in item) { 28 queue = [...queue, ...item.children]; 29 } 30 } 31 return result; 32 } 33 34 function getIndexedItem(index, key) { 35 if (typeof key === "object" && key != null) { 36 return index.get(key.uid); 37 } 38 if (typeof key === "string") { 39 return index.get(key); 40 } 41 return null; 42 } 43 44 async function getXScopes(sourceId, getSourceMap) { 45 if (xScopes.has(sourceId)) { 46 return xScopes.get(sourceId); 47 } 48 const map = await getSourceMap(sourceId); 49 if (!map || !map.xScopes) { 50 xScopes.set(sourceId, null); 51 return null; 52 } 53 const { code_section_offset, debug_info } = map.xScopes; 54 const xScope = { 55 code_section_offset, 56 debug_info, 57 idIndex: indexLinkingNames(debug_info), 58 sources: map.sources, 59 }; 60 xScopes.set(sourceId, xScope); 61 return xScope; 62 } 63 64 function isInRange(item, pc) { 65 if ("ranges" in item) { 66 return item.ranges.some(r => r[0] <= pc && pc < r[1]); 67 } 68 if ("high_pc" in item) { 69 return item.low_pc <= pc && pc < item.high_pc; 70 } 71 return false; 72 } 73 74 function decodeExprAt(expr, pc) { 75 if (typeof expr === "string") { 76 return decodeExpr(expr); 77 } 78 const foundAt = expr.find(i => i.range[0] <= pc && pc < i.range[1]); 79 return foundAt ? decodeExpr(foundAt.expr) : null; 80 } 81 82 function getVariables(scope, pc) { 83 const vars = scope.children 84 ? scope.children.reduce((result, item) => { 85 switch (item.tag) { 86 case "variable": 87 case "formal_parameter": 88 result.push({ 89 name: item.name || "", 90 expr: item.location ? decodeExprAt(item.location, pc) : null, 91 }); 92 break; 93 case "lexical_block": { 94 // FIXME build scope blocks (instead of combining) 95 const tmp = getVariables(item, pc); 96 result = [...tmp.vars, ...result]; 97 break; 98 } 99 } 100 return result; 101 }, []) 102 : []; 103 const frameBase = scope.frame_base ? decodeExpr(scope.frame_base) : null; 104 return { 105 vars, 106 frameBase, 107 }; 108 } 109 110 function filterScopes(items, pc, lastItem, index) { 111 if (!items) { 112 return []; 113 } 114 return items.reduce((result, item) => { 115 switch (item.tag) { 116 case "compile_unit": 117 if (isInRange(item, pc)) { 118 result = [ 119 ...result, 120 ...filterScopes(item.children, pc, lastItem, index), 121 ]; 122 } 123 break; 124 case "namespace": 125 case "structure_type": 126 case "union_type": 127 result = [ 128 ...result, 129 ...filterScopes(item.children, pc, lastItem, index), 130 ]; 131 break; 132 case "subprogram": 133 if (isInRange(item, pc)) { 134 const s = { 135 id: item.linkage_name, 136 name: item.name, 137 variables: getVariables(item, pc), 138 }; 139 result = [...result, s, ...filterScopes(item.children, pc, s, index)]; 140 } 141 break; 142 case "inlined_subroutine": 143 if (isInRange(item, pc)) { 144 const linkedItem = getIndexedItem(index, item.abstract_origin); 145 const s = { 146 id: item.abstract_origin, 147 name: linkedItem ? linkedItem.name : void 0, 148 variables: getVariables(item, pc), 149 }; 150 if (lastItem) { 151 lastItem.file = item.call_file; 152 lastItem.line = item.call_line; 153 } 154 result = [...result, s, ...filterScopes(item.children, pc, s, index)]; 155 } 156 break; 157 } 158 return result; 159 }, []); 160 } 161 162 class XScope { 163 xScope; 164 sourceMapContext; 165 166 constructor(xScopeData, sourceMapContext) { 167 this.xScope = xScopeData; 168 this.sourceMapContext = sourceMapContext; 169 } 170 171 search(generatedLocation) { 172 const { code_section_offset, debug_info, sources, idIndex } = this.xScope; 173 const pc = generatedLocation.line - (code_section_offset || 0); 174 const scopes = filterScopes(debug_info, pc, null, idIndex); 175 scopes.reverse(); 176 177 return scopes.map(i => { 178 if (!("file" in i)) { 179 return { 180 displayName: i.name || "", 181 variables: i.variables, 182 }; 183 } 184 const sourceId = this.sourceMapContext.generatedToOriginalId( 185 generatedLocation.sourceId, 186 sources[i.file || 0] 187 ); 188 return { 189 displayName: i.name || "", 190 variables: i.variables, 191 location: { 192 line: i.line || 0, 193 sourceId, 194 }, 195 }; 196 }); 197 } 198 } 199 200 async function getWasmXScopes(sourceId, sourceMapContext) { 201 const { getSourceMap } = sourceMapContext; 202 const xScopeData = await getXScopes(sourceId, getSourceMap); 203 if (!xScopeData) { 204 return null; 205 } 206 return new XScope(xScopeData, sourceMapContext); 207 } 208 209 function clearWasmXScopes() { 210 xScopes.clear(); 211 } 212 213 module.exports = { 214 getWasmXScopes, 215 clearWasmXScopes, 216 };