Commit 237d1280 authored by Erik Luo's avatar Erik Luo Committed by Commit Bot

DevTools: add method to detect support for side-effect checks

Bug: 810176
Change-Id: Iccad1213fbaabb465f42dbdf75273c0c2fa8913e
Reviewed-on: https://chromium-review.googlesource.com/941001
Commit-Queue: Erik Luo <luoe@chromium.org>
Reviewed-by: default avatarDmitry Gozman <dgozman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#543281}
parent 29e02dc1
Test frontend's side-effect support check for compatibility.
Testing expression (async function(){ await 1; })() with throwOnSideEffect true
Exception: EvalError: Possible side-effect in debug-evaluate
Testing expression (async function(){ await 1; })() with throwOnSideEffect false
Result: Promise
Testing expression 1 + 1 with throwOnSideEffect true
Result: 2
Testing expression 1 + 1 with throwOnSideEffect false
Result: 2
Does the runtime support side effect checks? true
Clearing cached side effect support
Set timer for test function.
Script execution paused.
Testing expression (async function(){ await 1; })() with throwOnSideEffect true
Exception: EvalError: Possible side-effect in debug-evaluate
Testing expression (async function(){ await 1; })() with throwOnSideEffect false
Result: Promise
Testing expression 1 + 1 with throwOnSideEffect true
Result: 2
Testing expression 1 + 1 with throwOnSideEffect false
Result: 2
Does the runtime support side effect checks? true
Script execution resumed.
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
(async function() {
await TestRunner.loadModule('sources_test_runner');
TestRunner.addResult("Test frontend's side-effect support check for compatibility.\n");
const executionContext = UI.context.flavor(SDK.ExecutionContext);
const expressionWithSideEffect = '(async function(){ await 1; })()';
const expressionWithoutSideEffect = '1 + 1';
await runtimeTestCase(expressionWithSideEffect, /* throwOnSideEffect */ true);
await runtimeTestCase(expressionWithSideEffect, /* throwOnSideEffect */ false);
await runtimeTestCase(expressionWithoutSideEffect, /* throwOnSideEffect */ true);
await runtimeTestCase(expressionWithoutSideEffect, /* throwOnSideEffect */ false);
let supports = executionContext.runtimeModel.hasSideEffectSupport();
TestRunner.addResult(`Does the runtime support side effect checks? ${supports}`);
TestRunner.addResult(`\nClearing cached side effect support`);
executionContext.runtimeModel._hasSideEffectSupport = null;
// Debugger evaluateOnCallFrame test.
await TestRunner.evaluateInPagePromise(`
function testFunction()
{
debugger;
}
`);
await SourcesTestRunner.startDebuggerTestPromise();
await SourcesTestRunner.runTestFunctionAndWaitUntilPausedPromise();
await debuggerTestCase(expressionWithSideEffect, /* throwOnSideEffect */ true);
await debuggerTestCase(expressionWithSideEffect, /* throwOnSideEffect */ false);
await debuggerTestCase(expressionWithoutSideEffect, /* throwOnSideEffect */ true);
await debuggerTestCase(expressionWithoutSideEffect, /* throwOnSideEffect */ false);
supports = executionContext.runtimeModel.hasSideEffectSupport();
TestRunner.addResult(`Does the runtime support side effect checks? ${supports}`);
SourcesTestRunner.completeDebuggerTest();
async function runtimeTestCase(expression, throwOnSideEffect) {
TestRunner.addResult(`\nTesting expression ${expression} with throwOnSideEffect ${throwOnSideEffect}`);
const result = await executionContext.evaluate({expression, throwOnSideEffect});
printDetails(result);
}
async function debuggerTestCase(expression, throwOnSideEffect) {
TestRunner.addResult(`\nTesting expression ${expression} with throwOnSideEffect ${throwOnSideEffect}`);
const result = await executionContext.debuggerModel.selectedCallFrame().evaluate({expression, throwOnSideEffect});
printDetails(result);
}
async function printDetails(result) {
if (result.error) {
TestRunner.addResult(`FAIL - Error: ${result.error}`);
} else if (result.exceptionDetails) {
let exceptionDescription = result.exceptionDetails.exception.description;
TestRunner.addResult(`Exception: ${exceptionDescription.split("\n")[0]}`);
} else if (result.object) {
let objectDescription = result.object.description;
TestRunner.addResult(`Result: ${objectDescription}`);
}
}
})();
......@@ -1292,6 +1292,12 @@ SDK.DebuggerModel.CallFrame = class {
* @return {!Promise<!SDK.RuntimeModel.EvaluationResult>}
*/
async evaluate(options) {
const runtimeModel = this.debuggerModel.runtimeModel();
if (options.throwOnSideEffect &&
(runtimeModel.hasSideEffectSupport() === false ||
(runtimeModel.hasSideEffectSupport() === null && !await runtimeModel.checkSideEffectSupport())))
return {error: 'Side-effect checks not supported by backend.'};
const response = await this.debuggerModel._agent.invoke_evaluateOnCallFrame({
callFrameId: this.id,
expression: options.expression,
......@@ -1300,17 +1306,14 @@ SDK.DebuggerModel.CallFrame = class {
silent: options.silent,
returnByValue: options.returnByValue,
generatePreview: options.generatePreview,
throwOnSideEffect: false
throwOnSideEffect: options.throwOnSideEffect
});
const error = response[Protocol.Error];
if (error) {
console.error(error);
return {error: error};
}
return {
object: this.debuggerModel.runtimeModel().createRemoteObject(response.result),
exceptionDetails: response.exceptionDetails
};
return {object: runtimeModel.createRemoteObject(response.result), exceptionDetails: response.exceptionDetails};
}
async restart() {
......
......@@ -44,6 +44,8 @@ SDK.RuntimeModel = class extends SDK.SDKModel {
/** @type {!Map<number, !SDK.ExecutionContext>} */
this._executionContextById = new Map();
this._executionContextComparator = SDK.ExecutionContext.comparator;
/** @type {?boolean} */
this._hasSideEffectSupport = null;
if (Common.moduleSetting('customFormatters').get())
this._agent.setCustomObjectFormatterEnabled(true);
......@@ -466,10 +468,46 @@ SDK.RuntimeModel = class extends SDK.SDKModel {
return 0;
return this.executionContextIdForScriptId(stackTrace.callFrames[0].scriptId);
}
/**
* @return {?boolean}
*/
hasSideEffectSupport() {
return this._hasSideEffectSupport;
}
/**
* @return {!Promise<boolean>}
*/
async checkSideEffectSupport() {
const testContext = this.executionContexts().peekLast();
if (!testContext)
return false;
// Check for a positive throwOnSideEffect response without triggering side effects.
const response = await this._agent.invoke_evaluate(
{expression: SDK.RuntimeModel._sideEffectTestExpression, contextId: testContext.id, throwOnSideEffect: true});
const exceptionDetails = !response[Protocol.Error] && response.exceptionDetails;
const supports =
!!(exceptionDetails && exceptionDetails.exception &&
exceptionDetails.exception.description.startsWith('EvalError: Possible side-effect in debug-evaluate'));
this._hasSideEffectSupport = supports;
return supports;
}
};
SDK.SDKModel.register(SDK.RuntimeModel, SDK.Target.Capability.JS, true);
/**
* This expression:
* - IMPORTANT: must not actually cause user-visible or JS-visible side-effects.
* - Must throw when evaluated with `throwOnSideEffect: true`.
* - Must be valid when run from any ExecutionContext that supports `throwOnSideEffect`.
* @const
* @type {string}
*/
SDK.RuntimeModel._sideEffectTestExpression = '(async function(){ await 1; })()';
/** @enum {symbol} */
SDK.RuntimeModel.Events = {
ExecutionContextCreated: Symbol('ExecutionContextCreated'),
......@@ -498,7 +536,8 @@ SDK.RuntimeModel.CompileScriptResult;
* includeCommandLineAPI: (boolean|undefined),
* silent: (boolean|undefined),
* returnByValue: (boolean|undefined),
* generatePreview: (boolean|undefined)
* generatePreview: (boolean|undefined),
* throwOnSideEffect: (boolean|undefined)
* }}
*/
SDK.RuntimeModel.EvaluationOptions;
......@@ -677,7 +716,19 @@ SDK.ExecutionContext = class {
// FIXME: It will be moved to separate ExecutionContext.
if (this.debuggerModel.selectedCallFrame())
return this.debuggerModel.evaluateOnSelectedCallFrame(options);
return this._evaluateGlobal(options, userGesture, awaitPromise);
if (!options.throwOnSideEffect || this.runtimeModel.hasSideEffectSupport())
return this._evaluateGlobal(options, userGesture, awaitPromise);
/** @type {!SDK.RuntimeModel.EvaluationResult} */
const unsupportedError = {error: 'Side-effect checks not supported by backend.'};
if (this.runtimeModel.hasSideEffectSupport() === false)
return Promise.resolve(unsupportedError);
return this.runtimeModel.checkSideEffectSupport().then(() => {
if (this.runtimeModel.hasSideEffectSupport())
return this._evaluateGlobal(options, userGesture, awaitPromise);
return Promise.resolve(unsupportedError);
});
}
/**
......@@ -719,7 +770,8 @@ SDK.ExecutionContext = class {
returnByValue: options.returnByValue,
generatePreview: options.generatePreview,
userGesture: userGesture,
awaitPromise: awaitPromise
awaitPromise: awaitPromise,
throwOnSideEffect: options.throwOnSideEffect
});
const error = response[Protocol.Error];
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment