Skip to content

Commit

Permalink
[ddc] Add debugger runtime API
Browse files Browse the repository at this point in the history
Define debugger runtime API so the debugger can display objects without knowing DDC implementation details.

- Add new DDC debugger runtime API in `debugger.dart`
- Add helpers for getting some type information in new and old type systems
- Add tests


Closes: #52773
Change-Id: I8efa4cacebb0d73ef58b5360979089cba2039379
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/311154
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Mark Zhou <markzipan@google.com>
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Anna Gringauze <annagrin@google.com>
  • Loading branch information
Anna Gringauze authored and Commit Queue committed Jul 21, 2023
1 parent 4854aab commit a322a97
Show file tree
Hide file tree
Showing 8 changed files with 1,600 additions and 50 deletions.
15 changes: 12 additions & 3 deletions pkg/dev_compiler/lib/src/kernel/compiler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1810,9 +1810,18 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>

js_ast.Expression _emitClassFieldSignature(Field field, Class fromClass) {
var type = _typeFromClass(field.type, field.enclosingClass!, fromClass);
var args = [_emitType(type)];
return runtimeCall(
field.isFinal ? 'finalFieldType(#)' : 'fieldType(#)', [args]);
var fieldType = field.type;
var uri = fieldType is InterfaceType
? _cacheUri(jsLibraryDebuggerName(fieldType.classNode.enclosingLibrary))
: null;
var isConst = js.boolean(field.isConst);
var isFinal = js.boolean(field.isFinal);

return uri == null
? js('{type: #, isConst: #, isFinal: #}',
[_emitType(type), isConst, isFinal])
: js('{type: #, isConst: #, isFinal: #, libraryUri: #}',
[_emitType(type), isConst, isFinal, uri]);
}

DartType _memberRuntimeType(Member member, Class fromClass) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class SetupCompilerOptions {
final ModuleFormat moduleFormat;
final fe.CompilerOptions options;
final bool soundNullSafety;
final bool canaryFeatures;

static fe.CompilerOptions _getOptions(
{required bool enableAsserts, required bool soundNullSafety}) {
Expand All @@ -95,12 +96,13 @@ class SetupCompilerOptions {
return options;
}

SetupCompilerOptions(
{bool enableAsserts = true,
this.soundNullSafety = true,
this.legacyCode = false,
this.moduleFormat = ModuleFormat.amd})
: options = _getOptions(
SetupCompilerOptions({
bool enableAsserts = true,
this.soundNullSafety = true,
this.legacyCode = false,
this.moduleFormat = ModuleFormat.amd,
this.canaryFeatures = false,
}) : options = _getOptions(
soundNullSafety: soundNullSafety, enableAsserts: enableAsserts) {
options.onDiagnostic = (fe.DiagnosticMessage m) {
diagnosticMessages.addAll(m.plainTextFormatted);
Expand Down Expand Up @@ -154,7 +156,7 @@ class TestCompiler {
experiments: experiments,
soundNullSafety: setup.soundNullSafety,
emitDebugMetadata: true,
canaryFeatures: false,
canaryFeatures: setup.canaryFeatures,
);
var coreTypes = compilerResult.coreTypes;

Expand Down Expand Up @@ -599,6 +601,68 @@ class TestDriver {
});
}

/// Evaluates a dart [expression] on a breakpoint.
///
/// [breakpointId] is the ID of the breakpoint from the source.
Future<String> evaluateDartExpression({
required String breakpointId,
required String expression,
}) async {
var dartLine = _findBreakpointLine(breakpointId);
return await _onBreakpoint(breakpointId, onPause: (event) async {
var result = await _evaluateDartExpression(
event,
expression,
dartLine,
);
return await stringifyRemoteObject(result);
});
}

/// Evaluates a js [expression] on a breakpoint.
///
/// [breakpointId] is the ID of the breakpoint from the source.
Future<String> evaluateJsExpression({
required String breakpointId,
required String expression,
}) async {
return await _onBreakpoint(breakpointId, onPause: (event) async {
var result = await _evaluateJsExpression(
event,
expression,
);
return await stringifyRemoteObject(result);
});
}

/// Evaluates a JavaScript [expression] on a breakpoint and validates result.
///
/// [breakpointId] is the ID of the breakpoint from the source.
/// [expression] is a dart runtime method call, i.e.
/// `dart.getLibraryMetadata(uri)`;
/// [expectedResult] is the JSON for the returned remote object.
///
/// Nested objects are not included in the result (they appear as `{}`),
/// only primitive values, lists or maps, etc.
///
/// TODO(annagrin): Add recursive check for nested objects.
Future<void> checkRuntime({
required String breakpointId,
required String expression,
required dynamic expectedResult,
}) async {
return await _onBreakpoint(breakpointId, onPause: (event) async {
var actual = await _evaluateJsExpression(event, expression);
expect(actual.json, expectedResult);
});
}

/// Evaluates a dart [expression] on a breakpoint and validates result.
///
/// [breakpointId] is the ID of the breakpoint from the source.
/// [expression] is a dart expression.
/// [expectedResult] is the JSON for the returned remote object.
/// [expectedError] is the error string if the error is expected.
Future<void> check(
{required String breakpointId,
required String expression,
Expand All @@ -609,45 +673,89 @@ class TestDriver {

var dartLine = _findBreakpointLine(breakpointId);
return await _onBreakpoint(breakpointId, onPause: (event) async {
// Retrieve the call frame and its scope variables.
var frame = event.getCallFrames().first;
var scope = await _collectScopeVariables(frame);

// Perform an incremental compile.
var result = await compiler.compileExpression(
input: input,
line: dartLine,
column: 1,
scope: scope,
expression: expression);

if (expectedError != null) {
var evalResult = await _evaluateDartExpression(
event,
expression,
dartLine,
);

var error = evalResult.json['error'];
if (error != null) {
expect(
result,
const TypeMatcher<TestCompilationResult>().having(
(_) => result.result, 'result', _matches(expectedError)));
setup.diagnosticMessages.clear();
setup.errors.clear();
return;
expectedError,
isNotNull,
reason: 'Unexpected expression evaluation failure:\n$error',
);
expect(error, _matches(expectedError!));
} else {
var actual = await stringifyRemoteObject(evalResult);
expect(actual, _matches(expectedResult!));
}
});
}

if (!result.isSuccess) {
throw Exception(
'Unexpected expression evaluation failure:\n${result.result}');
}
Future<wip.RemoteObject> _evaluateJsExpression(
wip.DebuggerPausedEvent event,
String expression, {
bool returnByValue = true,
}) async {
var frame = event.getCallFrames().first;

var loadModule = setup.moduleFormat == ModuleFormat.amd
? 'require'
: 'dart_library.import';

var jsExpression = '''
(function () {
try {
var sdk = $loadModule('dart_sdk');
var dart = sdk.dart;
var interceptors = sdk._interceptors;
return $expression;
} catch (error) {
return "Runtime API call failed: " + error.name +
": " + error.message + ": " + error.stack;
}
})()
''';

return await debugger.evaluateOnCallFrame(
frame.callFrameId,
jsExpression,
returnByValue: returnByValue,
);
}

// Evaluate the compiled expression.
var evalResult = await debugger.evaluateOnCallFrame(
frame.callFrameId, result.result!,
returnByValue: false);
Future<wip.RemoteObject> _evaluateDartExpression(
wip.DebuggerPausedEvent event,
String expression,
int dartLine, {
bool returnByValue = false,
}) async {
// Retrieve the call frame and its scope variables.
var frame = event.getCallFrames().first;
var scope = await _collectScopeVariables(frame);

var value = await stringifyRemoteObject(evalResult);
// Perform an incremental compile.
var result = await compiler.compileExpression(
input: input,
line: dartLine,
column: 1,
scope: scope,
expression: expression);

if (!result.isSuccess) {
setup.diagnosticMessages.clear();
setup.errors.clear();
return wip.RemoteObject({'error': result.result});
}

expect(
result,
const TypeMatcher<TestCompilationResult>()
.having((_) => value, 'result', _matches(expectedResult!)));
});
// Evaluate the compiled expression.
return await debugger.evaluateOnCallFrame(
frame.callFrameId,
result.result!,
returnByValue: returnByValue,
);
}

/// Generate simple string representation of a RemoteObject that closely
Expand Down
Loading

0 comments on commit a322a97

Please sign in to comment.