This repository has been archived by the owner on Jan 11, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 762
[scopes] Batch generated locations #5892
Closed
jasonLaster
wants to merge
2
commits into
firefox-devtools:master
from
jasonLaster:batch-generated-locs
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// @flow | ||
import { has } from "lodash"; | ||
import { locColumn } from "./locColumn"; | ||
|
||
export function buildGeneratedBindingList( | ||
scopes: Scope, | ||
generatedAstScopes: SourceScope[], | ||
thisBinding: ?BindingContents | ||
): Array<GeneratedBindingLocation> { | ||
// The server's binding data doesn't include general 'this' binding | ||
// information, so we manually inject the one 'this' binding we have into | ||
// the normal binding data we are working with. | ||
const frameThisOwner = generatedAstScopes.find( | ||
generated => "this" in generated.bindings | ||
); | ||
|
||
const clientScopes = []; | ||
for (let s = scopes; s; s = s.parent) { | ||
const bindings = s.bindings | ||
? Object.assign({}, ...s.bindings.arguments, s.bindings.variables) | ||
: {}; | ||
|
||
clientScopes.push(bindings); | ||
} | ||
|
||
const generatedMainScopes = generatedAstScopes.slice(0, -2); | ||
const generatedGlobalScopes = generatedAstScopes.slice(-2); | ||
|
||
const clientMainScopes = clientScopes.slice(0, generatedMainScopes.length); | ||
const clientGlobalScopes = clientScopes.slice(generatedMainScopes.length); | ||
|
||
// Map the main parsed script body using the nesting hierarchy of the | ||
// generated and client scopes. | ||
const generatedBindings = generatedMainScopes.reduce((acc, generated, i) => { | ||
const bindings = clientMainScopes[i]; | ||
|
||
if (generated === frameThisOwner && thisBinding) { | ||
bindings.this = { | ||
value: thisBinding | ||
}; | ||
} | ||
|
||
for (const name of Object.keys(generated.bindings)) { | ||
const { refs } = generated.bindings[name]; | ||
for (const loc of refs) { | ||
acc.push({ | ||
name, | ||
loc, | ||
desc: bindings[name] || null | ||
}); | ||
} | ||
} | ||
return acc; | ||
}, []); | ||
|
||
// Bindings in the global/lexical global of the generated code may or | ||
// may not be the real global if the generated code is running inside | ||
// of an evaled context. To handle this, we just look up the client scope | ||
// hierarchy to find the closest binding with that name. | ||
for (const generated of generatedGlobalScopes) { | ||
for (const name of Object.keys(generated.bindings)) { | ||
const { refs } = generated.bindings[name]; | ||
for (const loc of refs) { | ||
const bindings = clientGlobalScopes.find(b => has(b, name)); | ||
|
||
if (bindings) { | ||
generatedBindings.push({ | ||
name, | ||
loc, | ||
desc: bindings[name] | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Sort so we can binary-search. | ||
return generatedBindings.sort((a, b) => { | ||
const aStart = a.loc.start; | ||
const bStart = a.loc.start; | ||
|
||
if (aStart.line === bStart.line) { | ||
return locColumn(aStart) - locColumn(bStart); | ||
} | ||
return aStart.line - bStart.line; | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// @flow | ||
|
||
import { isReliableScope } from "./isReliableScope"; | ||
import { buildGeneratedBindingList } from "./buildGeneratedBindingList"; | ||
import { findGeneratedBinding } from "./findGeneratedBinding"; | ||
import { generateClientScope } from "./generateClientScope"; | ||
import { flatMap, uniqBy } from "lodash"; | ||
import type { | ||
Frame, | ||
Scope, | ||
Source, | ||
BindingContents, | ||
ScopeBindings | ||
} from "../../../types"; | ||
|
||
export async function buildMappedScopes( | ||
source: Source, | ||
frame: Frame, | ||
originalAstScopes: Scope, | ||
generatedAstScopes: Scope, | ||
scopes: Scope, | ||
sourceMaps: any, | ||
client: any | ||
): Promise<?{ | ||
mappings: { | ||
[string]: string | ||
}, | ||
scope: OriginalScope | ||
}> { | ||
const generatedAstBindings = buildGeneratedBindingList( | ||
scopes, | ||
generatedAstScopes, | ||
frame.this | ||
); | ||
|
||
const expressionLookup = {}; | ||
const mappedOriginalScopes = []; | ||
|
||
const generatedLocations = await getGeneratedPositions( | ||
originalAstScopes, | ||
source, | ||
sourceMaps | ||
); | ||
|
||
for (const item of originalAstScopes) { | ||
const generatedBindings = {}; | ||
|
||
for (const name of Object.keys(item.bindings)) { | ||
const binding = item.bindings[name]; | ||
|
||
const result = await findGeneratedBinding( | ||
generatedLocations, | ||
client, | ||
name, | ||
binding, | ||
generatedAstBindings | ||
); | ||
|
||
if (result) { | ||
generatedBindings[name] = result.grip; | ||
|
||
if ( | ||
binding.refs.length !== 0 && | ||
// These are assigned depth-first, so we don't want shadowed | ||
// bindings in parent scopes overwriting the expression. | ||
!Object.prototype.hasOwnProperty.call(expressionLookup, name) | ||
) { | ||
expressionLookup[name] = result.expression; | ||
} | ||
} | ||
} | ||
|
||
mappedOriginalScopes.push({ | ||
...item, | ||
generatedBindings | ||
}); | ||
} | ||
|
||
const mappedGeneratedScopes = generateClientScope( | ||
scopes, | ||
mappedOriginalScopes | ||
); | ||
|
||
return isReliableScope(mappedGeneratedScopes) | ||
? { mappings: expressionLookup, scope: mappedGeneratedScopes } | ||
: null; | ||
} | ||
|
||
async function getGeneratedPositions(originalAstScopes, source, sourceMaps) { | ||
const scopes = Object.values(originalAstScopes); | ||
const bindings = flatMap(scopes, ({ bindings }) => Object.values(bindings)); | ||
const refs = flatMap(bindings, ({ refs }) => refs); | ||
|
||
const locations = refs.reduce((positions, ref) => { | ||
positions.push(ref.start); | ||
positions.push(ref.end); | ||
if (ref.declaration) { | ||
positions.push(ref.declaration.start); | ||
positions.push(ref.declaration.end); | ||
} | ||
return positions; | ||
}, []); | ||
|
||
const originalLocations = uniqBy(locations, loc => | ||
Object.values(loc).join("-") | ||
); | ||
|
||
let sourcesMap = { [source.id]: source.url }; | ||
return sourceMaps.batchGeneratedLocations(originalLocations, sourcesMap); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ | ||
|
||
// @flow | ||
|
||
// eslint-disable-next-line max-len | ||
import { findGeneratedBindingFromPosition } from "./findGeneratedBindingFromPosition"; | ||
|
||
import type { GeneratedBindingLocation } from "./types"; | ||
import type { BindingData } from "../../workers/parser"; | ||
|
||
export async function i( | ||
generatedLocations: any, | ||
client: any, | ||
name: string, | ||
originalBinding: BindingData, | ||
generatedAstBindings: Array<GeneratedBindingLocation> | ||
): Promise<?{ | ||
grip: BindingContents, | ||
expression: string | null | ||
}> { | ||
// If there are no references to the implicits, then we have no way to | ||
// even attempt to map it back to the original since there is no location | ||
// data to use. Bail out instead of just showing it as unmapped. | ||
if ( | ||
originalBinding.type === "implicit" && | ||
!originalBinding.refs.some(item => item.type === "ref") | ||
) { | ||
return null; | ||
} | ||
|
||
const { refs } = originalBinding; | ||
|
||
const genContent = await refs.reduce(async (acc, pos) => { | ||
const result = await acc; | ||
if (result) { | ||
return result; | ||
} | ||
|
||
return await findGeneratedBindingFromPosition( | ||
generatedLocations, | ||
client, | ||
pos, | ||
name, | ||
originalBinding.type, | ||
generatedAstBindings | ||
); | ||
}, null); | ||
|
||
if (genContent && genContent.desc) { | ||
return { | ||
grip: genContent.desc, | ||
expression: genContent.expression | ||
}; | ||
} else if (genContent) { | ||
// If there is no descriptor for 'this', then this is not the top-level | ||
// 'this' that the server gave us a binding for, and we can just ignore it. | ||
if (name === "this") { | ||
return null; | ||
} | ||
|
||
// If the location is found but the descriptor is not, then it | ||
// means that the server scope information didn't match the scope | ||
// information from the DevTools parsed scopes. | ||
return { | ||
grip: { | ||
configurable: false, | ||
enumerable: true, | ||
writable: false, | ||
value: { | ||
type: "unscoped", | ||
unscoped: true, | ||
|
||
// HACK: Until support for "unscoped" lands in devtools-reps, | ||
// this will make these show as (unavailable). | ||
missingArguments: true | ||
} | ||
}, | ||
expression: null | ||
}; | ||
} | ||
|
||
// If no location mapping is found, then the map is bad, or | ||
// the map is okay but it original location is inside | ||
// of some scope, but the generated location is outside, leading | ||
// us to search for bindings that don't technically exist. | ||
return { | ||
grip: { | ||
configurable: false, | ||
enumerable: true, | ||
writable: false, | ||
value: { | ||
type: "unmapped", | ||
unmapped: true, | ||
|
||
// HACK: Until support for "unmapped" lands in devtools-reps, | ||
// this will make these show as (unavailable). | ||
missingArguments: true | ||
} | ||
}, | ||
expression: null | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may want to delay this, because this is the logic I'm already going to be changing to updating how the ranges work. Maybe it would make more sense for me to make that a batched lookup?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that works!