mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-09-11 14:46:37 +03:00
Debugger stop on Prepack Error (#2088)
Summary: Release Notes: Full integration with Nuclide UI coming in next PR. This PR sets up a pipeline for passing configs to the debugger, and uses that pipeline to specify what level of CompilerDiagnostic the debugger should break at. The debugger's diagnostic check sits within the `handleError` function and occurs before Prepack takes its own action. Current CLI experience: <img width="1680" alt="dbg-cli-example" src="https://user-images.githubusercontent.com/15720289/41180054-bd45d39e-6b21-11e8-8a24-9626adab737f.png"> Closes https://github.com/facebook/prepack/pull/2088 Differential Revision: D8355227 Pulled By: caiismyname fbshipit-source-id: 3ca457bcbb7697fd39073c3d2ddc88be92bfc662
This commit is contained in:
parent
e4875197fb
commit
d5bb04fb8e
@ -22,6 +22,7 @@ import { ObjectValue } from "./values/index.js";
|
||||
import { DebugServer } from "./debugger/server/Debugger.js";
|
||||
import type { DebugChannel } from "./debugger/server/channel/DebugChannel.js";
|
||||
import simplifyAndRefineAbstractValue from "./utils/simplifier.js";
|
||||
import invariant from "./invariant.js";
|
||||
|
||||
export default function(
|
||||
opts: RealmOptions = {},
|
||||
@ -30,10 +31,11 @@ export default function(
|
||||
): Realm {
|
||||
initializeSingletons();
|
||||
let r = new Realm(opts, statistics || new RealmStatistics());
|
||||
// Presence of debugChannel indicates we wish to use debugger.
|
||||
if (debugChannel) {
|
||||
if (debugChannel.debuggerIsAttached()) {
|
||||
r.debuggerInstance = new DebugServer(debugChannel, r);
|
||||
}
|
||||
invariant(debugChannel.debuggerIsAttached(), "Debugger intends to be used but is not attached.");
|
||||
invariant(opts.debuggerConfigArgs !== undefined, "Debugger intends to be used but does not have launch arguments.");
|
||||
r.debuggerInstance = new DebugServer(debugChannel, r, opts.debuggerConfigArgs);
|
||||
}
|
||||
|
||||
let i = r.intrinsics;
|
||||
|
@ -53,12 +53,12 @@ class PrepackDebugSession extends DebugSession {
|
||||
this._adapterChannel.registerChannelEvent(DebugMessage.STOPPED_RESPONSE, (response: DebuggerResponse) => {
|
||||
let result = response.result;
|
||||
invariant(result.kind === "stopped");
|
||||
this.sendEvent(
|
||||
new StoppedEvent(
|
||||
`${result.reason}: ${result.filePath} ${result.line}:${result.column}`,
|
||||
DebuggerConstants.PREPACK_THREAD_ID
|
||||
)
|
||||
);
|
||||
let message = `${result.reason}: ${result.filePath} ${result.line}:${result.column}`;
|
||||
// Append message if there exists one (for PP errors)
|
||||
if (result.message !== undefined) {
|
||||
message += `. ${result.message}`;
|
||||
}
|
||||
this.sendEvent(new StoppedEvent(message, DebuggerConstants.PREPACK_THREAD_ID));
|
||||
});
|
||||
this._adapterChannel.registerChannelEvent(DebugMessage.STEPINTO_RESPONSE, (response: DebuggerResponse) => {
|
||||
let result = response.result;
|
||||
|
@ -48,13 +48,20 @@ export class MessageMarshaller {
|
||||
return `${requestID} ${messageType} ${JSON.stringify(breakpoints)}`;
|
||||
}
|
||||
|
||||
marshallStoppedResponse(reason: StoppedReason, filePath: string, line: number, column: number): string {
|
||||
marshallStoppedResponse(
|
||||
reason: StoppedReason,
|
||||
filePath: string,
|
||||
line: number,
|
||||
column: number,
|
||||
message?: string
|
||||
): string {
|
||||
let result: StoppedResult = {
|
||||
kind: "stopped",
|
||||
reason: reason,
|
||||
filePath: filePath,
|
||||
line: line,
|
||||
column: column,
|
||||
message: message,
|
||||
};
|
||||
return `${this._lastRunRequestID} ${DebugMessage.STOPPED_RESPONSE} ${JSON.stringify(result)}`;
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
/* @flow strict */
|
||||
|
||||
import * as DebugProtocol from "vscode-debugprotocol";
|
||||
import type { Severity } from "../../errors.js";
|
||||
|
||||
export type DebuggerRequest = {
|
||||
id: number,
|
||||
@ -39,6 +40,10 @@ export type PrepackLaunchArguments = {
|
||||
exitCallback: () => void,
|
||||
};
|
||||
|
||||
export type DebuggerConfigArguments = {
|
||||
diagnosticSeverity?: Severity,
|
||||
};
|
||||
|
||||
export type Breakpoint = {
|
||||
filePath: string,
|
||||
line: number,
|
||||
@ -128,6 +133,7 @@ export type StoppedResult = {
|
||||
filePath: string,
|
||||
line: number,
|
||||
column: number,
|
||||
message?: string,
|
||||
};
|
||||
export type Scope = {
|
||||
name: string,
|
||||
@ -167,7 +173,7 @@ export type LaunchRequestArguments = {
|
||||
};
|
||||
|
||||
export type SteppingType = "Step Into" | "Step Over" | "Step Out";
|
||||
export type StoppedReason = "Entry" | "Breakpoint" | SteppingType;
|
||||
export type StoppedReason = "Entry" | "Breakpoint" | "Diagnostic" | SteppingType;
|
||||
|
||||
export type SourceData = {
|
||||
filePath: string,
|
||||
|
@ -50,6 +50,12 @@ function readCLIArguments(process, console): DebuggerCLIArguments {
|
||||
prepackArguments = args.shift().split(" ");
|
||||
} else if (arg === "sourceFile") {
|
||||
sourceFile = args.shift();
|
||||
} else if (arg === "diagnosticSeverity") {
|
||||
arg = args.shift();
|
||||
if (arg !== "FatalError" && arg !== "RecoverableError" && arg !== "Warning" && arg !== "Information") {
|
||||
console.error("Invalid debugger diagnostic severity level");
|
||||
}
|
||||
prepackArguments = prepackArguments.concat(["--debugDiagnosticSeverity", `${arg}`]);
|
||||
} else {
|
||||
console.error("Unknown argument: " + arg);
|
||||
process.exit(1);
|
||||
|
@ -24,6 +24,7 @@ import type {
|
||||
VariablesArguments,
|
||||
EvaluateArguments,
|
||||
SourceData,
|
||||
DebuggerConfigArguments,
|
||||
} from "./../common/types.js";
|
||||
import type { Realm } from "./../../realm.js";
|
||||
import { ExecutionContext } from "./../../realm.js";
|
||||
@ -38,9 +39,11 @@ import {
|
||||
DeclarativeEnvironmentRecord,
|
||||
ObjectEnvironmentRecord,
|
||||
} from "./../../environment.js";
|
||||
import { CompilerDiagnostic } from "../../errors.js";
|
||||
import type { Severity } from "../../errors.js";
|
||||
|
||||
export class DebugServer {
|
||||
constructor(channel: DebugChannel, realm: Realm) {
|
||||
constructor(channel: DebugChannel, realm: Realm, configArgs: DebuggerConfigArguments) {
|
||||
this._channel = channel;
|
||||
this._realm = realm;
|
||||
this._breakpointManager = new BreakpointManager();
|
||||
@ -48,6 +51,7 @@ export class DebugServer {
|
||||
this._stepManager = new SteppingManager(this._realm, /* default discard old steppers */ false);
|
||||
this._stopEventManager = new StopEventManager();
|
||||
this.waitForRun(undefined);
|
||||
this._diagnosticSeverity = configArgs.diagnosticSeverity || "FatalError";
|
||||
}
|
||||
// the collection of breakpoints
|
||||
_breakpointManager: BreakpointManager;
|
||||
@ -58,6 +62,8 @@ export class DebugServer {
|
||||
_stepManager: SteppingManager;
|
||||
_stopEventManager: StopEventManager;
|
||||
_lastExecuted: SourceData;
|
||||
// Severity at which debugger will break when CompilerDiagnostics are generated. Default is Fatal.
|
||||
_diagnosticSeverity: Severity;
|
||||
|
||||
/* Block until adapter says to run
|
||||
/* ast: the current ast node we are stopped on
|
||||
@ -291,6 +297,41 @@ export class DebugServer {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
Displays PP error message, then waits for user to run the program to
|
||||
continue (similar to a breakpoint).
|
||||
*/
|
||||
handlePrepackError(diagnostic: CompilerDiagnostic) {
|
||||
invariant(diagnostic.location && diagnostic.location.source);
|
||||
let location = diagnostic.location;
|
||||
let message = `${diagnostic.severity} ${diagnostic.errorCode}: ${diagnostic.message}`;
|
||||
this._channel.sendStoppedResponse(
|
||||
"Diagnostic",
|
||||
location.source || "",
|
||||
location.start.line,
|
||||
location.start.column,
|
||||
message
|
||||
);
|
||||
// No ast parameter b/c you cannot stepInto/Over a line that's causing an exception
|
||||
this.waitForRun();
|
||||
}
|
||||
|
||||
// Return whether the debugger should stop on a CompilerDiagnostic of a given severity.
|
||||
shouldStopForSeverity(severity: Severity): boolean {
|
||||
switch (this._diagnosticSeverity) {
|
||||
case "Information":
|
||||
return true;
|
||||
case "Warning":
|
||||
return severity !== "Information";
|
||||
case "RecoverableError":
|
||||
return severity === "RecoverableError" || severity === "FatalError";
|
||||
case "FatalError":
|
||||
return severity === "FatalError";
|
||||
default:
|
||||
invariant(false, "Unexpected severity type");
|
||||
}
|
||||
}
|
||||
|
||||
shutdown() {
|
||||
// clean the channel pipes
|
||||
this._channel.shutdown();
|
||||
|
@ -75,8 +75,8 @@ export class DebugChannel {
|
||||
this.writeOut(this._marshaller.marshallBreakpointAcknowledge(requestID, messageType, args.breakpoints));
|
||||
}
|
||||
|
||||
sendStoppedResponse(reason: StoppedReason, filePath: string, line: number, column: number): void {
|
||||
this.writeOut(this._marshaller.marshallStoppedResponse(reason, filePath, line, column));
|
||||
sendStoppedResponse(reason: StoppedReason, filePath: string, line: number, column: number, message?: string): void {
|
||||
this.writeOut(this._marshaller.marshallStoppedResponse(reason, filePath, line, column, message));
|
||||
}
|
||||
|
||||
sendStackframeResponse(requestID: number, stackframes: Array<Stackframe>): void {
|
||||
|
@ -10,6 +10,7 @@
|
||||
/* @flow strict */
|
||||
|
||||
import type { ErrorHandler } from "./errors.js";
|
||||
import type { DebuggerConfigArguments } from "./debugger/common/types";
|
||||
|
||||
export type Compatibility =
|
||||
| "browser"
|
||||
@ -73,6 +74,7 @@ export type RealmOptions = {
|
||||
reactOptimizeNestedFunctions?: boolean,
|
||||
stripFlow?: boolean,
|
||||
abstractValueImpliesMax?: number,
|
||||
debuggerConfigArgs?: DebuggerConfigArguments,
|
||||
};
|
||||
|
||||
export type SerializerOptions = {
|
||||
|
@ -30,6 +30,7 @@ import invariant from "./invariant";
|
||||
import zipFactory from "node-zip";
|
||||
import path from "path";
|
||||
import JSONTokenizer from "./utils/JSONTokenizer.js";
|
||||
import type { DebuggerConfigArguments } from "./debugger/common/types";
|
||||
|
||||
// Prepack helper
|
||||
declare var __residual: any;
|
||||
@ -78,6 +79,7 @@ function run(
|
||||
--version Output the version number.
|
||||
--repro Create a zip file with all information needed to reproduce a Prepack run"
|
||||
--cpuprofile Create a CPU profile file for the run that can be loaded into the Chrome JavaScript CPU Profile viewer",
|
||||
--debugDiagnosticSeverity FatalError | RecoverableError | Warning | Information (default = FatalError). Diagnostic level at which debugger will stop
|
||||
`;
|
||||
let args = Array.from(process.argv);
|
||||
args.splice(0, 2);
|
||||
@ -126,6 +128,7 @@ function run(
|
||||
reproFileNames.push(fileName);
|
||||
return path.basename(fileName);
|
||||
};
|
||||
let debuggerConfigArgs: DebuggerConfigArguments = {};
|
||||
while (args.length) {
|
||||
let arg = args.shift();
|
||||
if (!arg.startsWith("--")) {
|
||||
@ -259,6 +262,14 @@ function run(
|
||||
invariantLevel = parseInt(invariantLevelString, 10);
|
||||
reproArguments.push("--invariantLevel", invariantLevel.toString());
|
||||
break;
|
||||
case "debugDiagnosticSeverity":
|
||||
arg = args.shift();
|
||||
invariant(
|
||||
arg === "FatalError" || arg === "RecoverableError" || arg === "Warning" || arg === "Information",
|
||||
`Invalid debugger diagnostic severity: ${arg}`
|
||||
);
|
||||
debuggerConfigArgs.diagnosticSeverity = arg;
|
||||
break;
|
||||
case "help":
|
||||
const options = [
|
||||
"-- | input.js",
|
||||
@ -341,6 +352,7 @@ fi
|
||||
reactOutput,
|
||||
invariantMode,
|
||||
invariantLevel,
|
||||
debuggerConfigArgs,
|
||||
},
|
||||
flags
|
||||
);
|
||||
|
@ -20,6 +20,7 @@ import type {
|
||||
} from "./options";
|
||||
import { Realm } from "./realm.js";
|
||||
import invariant from "./invariant.js";
|
||||
import type { DebuggerConfigArguments } from "./debugger/common/types";
|
||||
|
||||
export type PrepackOptions = {|
|
||||
additionalGlobals?: Realm => void,
|
||||
@ -63,6 +64,7 @@ export type PrepackOptions = {|
|
||||
debugInFilePath?: string,
|
||||
debugOutFilePath?: string,
|
||||
abstractValueImpliesMax?: number,
|
||||
debuggerConfigArgs?: DebuggerConfigArguments,
|
||||
|};
|
||||
|
||||
export function getRealmOptions({
|
||||
@ -86,6 +88,7 @@ export function getRealmOptions({
|
||||
timeout,
|
||||
maxStackDepth,
|
||||
abstractValueImpliesMax,
|
||||
debuggerConfigArgs,
|
||||
}: PrepackOptions): RealmOptions {
|
||||
return {
|
||||
compatibility,
|
||||
@ -108,6 +111,7 @@ export function getRealmOptions({
|
||||
timeout,
|
||||
maxStackDepth,
|
||||
abstractValueImpliesMax,
|
||||
debuggerConfigArgs,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1645,6 +1645,13 @@ export class Realm {
|
||||
let stack = error._SafeGetDataPropertyValue("stack");
|
||||
if (stack instanceof StringValue) diagnostic.callStack = stack.value;
|
||||
}
|
||||
|
||||
// If debugger is attached, give it a first crack so that it can
|
||||
// stop execution for debugging before PP exits.
|
||||
if (this.debuggerInstance && this.debuggerInstance.shouldStopForSeverity(diagnostic.severity)) {
|
||||
this.debuggerInstance.handlePrepackError(diagnostic);
|
||||
}
|
||||
|
||||
// Default behaviour is to bail on the first error
|
||||
let errorHandler = this.errorHandler;
|
||||
if (!errorHandler) {
|
||||
|
@ -49,6 +49,8 @@ import type {
|
||||
BabelNodeSourceLocation,
|
||||
} from "babel-types";
|
||||
import type { Bindings, Effects, EvaluationResult, PropertyBindings, CreatedObjects, Realm } from "./realm.js";
|
||||
import { CompilerDiagnostic } from "./errors.js";
|
||||
import type { Severity } from "./errors.js";
|
||||
|
||||
export const ElementSize = {
|
||||
Float32: 4,
|
||||
@ -352,6 +354,8 @@ export type ReactComponentTreeConfig = {
|
||||
|
||||
export type DebugServerType = {
|
||||
checkForActions: BabelNode => void,
|
||||
handlePrepackError: CompilerDiagnostic => void,
|
||||
shouldStopForSeverity: Severity => boolean,
|
||||
shutdown: () => void,
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user