Skip to content

Commit a322a97

Browse files
Anna GringauzeCommit Queue
authored andcommitted
[ddc] Add debugger runtime API
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 <[email protected]> Reviewed-by: Sigmund Cherem <[email protected]> Reviewed-by: Mark Zhou <[email protected]> Reviewed-by: Stephen Adams <[email protected]> Commit-Queue: Anna Gringauze <[email protected]>
1 parent 4854aab commit a322a97

File tree

8 files changed

+1600
-50
lines changed

8 files changed

+1600
-50
lines changed

pkg/dev_compiler/lib/src/kernel/compiler.dart

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,9 +1810,18 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
18101810

18111811
js_ast.Expression _emitClassFieldSignature(Field field, Class fromClass) {
18121812
var type = _typeFromClass(field.type, field.enclosingClass!, fromClass);
1813-
var args = [_emitType(type)];
1814-
return runtimeCall(
1815-
field.isFinal ? 'finalFieldType(#)' : 'fieldType(#)', [args]);
1813+
var fieldType = field.type;
1814+
var uri = fieldType is InterfaceType
1815+
? _cacheUri(jsLibraryDebuggerName(fieldType.classNode.enclosingLibrary))
1816+
: null;
1817+
var isConst = js.boolean(field.isConst);
1818+
var isFinal = js.boolean(field.isFinal);
1819+
1820+
return uri == null
1821+
? js('{type: #, isConst: #, isFinal: #}',
1822+
[_emitType(type), isConst, isFinal])
1823+
: js('{type: #, isConst: #, isFinal: #, libraryUri: #}',
1824+
[_emitType(type), isConst, isFinal, uri]);
18161825
}
18171826

18181827
DartType _memberRuntimeType(Member member, Class fromClass) {

pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_suite.dart

Lines changed: 148 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class SetupCompilerOptions {
7474
final ModuleFormat moduleFormat;
7575
final fe.CompilerOptions options;
7676
final bool soundNullSafety;
77+
final bool canaryFeatures;
7778

7879
static fe.CompilerOptions _getOptions(
7980
{required bool enableAsserts, required bool soundNullSafety}) {
@@ -95,12 +96,13 @@ class SetupCompilerOptions {
9596
return options;
9697
}
9798

98-
SetupCompilerOptions(
99-
{bool enableAsserts = true,
100-
this.soundNullSafety = true,
101-
this.legacyCode = false,
102-
this.moduleFormat = ModuleFormat.amd})
103-
: options = _getOptions(
99+
SetupCompilerOptions({
100+
bool enableAsserts = true,
101+
this.soundNullSafety = true,
102+
this.legacyCode = false,
103+
this.moduleFormat = ModuleFormat.amd,
104+
this.canaryFeatures = false,
105+
}) : options = _getOptions(
104106
soundNullSafety: soundNullSafety, enableAsserts: enableAsserts) {
105107
options.onDiagnostic = (fe.DiagnosticMessage m) {
106108
diagnosticMessages.addAll(m.plainTextFormatted);
@@ -154,7 +156,7 @@ class TestCompiler {
154156
experiments: experiments,
155157
soundNullSafety: setup.soundNullSafety,
156158
emitDebugMetadata: true,
157-
canaryFeatures: false,
159+
canaryFeatures: setup.canaryFeatures,
158160
);
159161
var coreTypes = compilerResult.coreTypes;
160162

@@ -599,6 +601,68 @@ class TestDriver {
599601
});
600602
}
601603

604+
/// Evaluates a dart [expression] on a breakpoint.
605+
///
606+
/// [breakpointId] is the ID of the breakpoint from the source.
607+
Future<String> evaluateDartExpression({
608+
required String breakpointId,
609+
required String expression,
610+
}) async {
611+
var dartLine = _findBreakpointLine(breakpointId);
612+
return await _onBreakpoint(breakpointId, onPause: (event) async {
613+
var result = await _evaluateDartExpression(
614+
event,
615+
expression,
616+
dartLine,
617+
);
618+
return await stringifyRemoteObject(result);
619+
});
620+
}
621+
622+
/// Evaluates a js [expression] on a breakpoint.
623+
///
624+
/// [breakpointId] is the ID of the breakpoint from the source.
625+
Future<String> evaluateJsExpression({
626+
required String breakpointId,
627+
required String expression,
628+
}) async {
629+
return await _onBreakpoint(breakpointId, onPause: (event) async {
630+
var result = await _evaluateJsExpression(
631+
event,
632+
expression,
633+
);
634+
return await stringifyRemoteObject(result);
635+
});
636+
}
637+
638+
/// Evaluates a JavaScript [expression] on a breakpoint and validates result.
639+
///
640+
/// [breakpointId] is the ID of the breakpoint from the source.
641+
/// [expression] is a dart runtime method call, i.e.
642+
/// `dart.getLibraryMetadata(uri)`;
643+
/// [expectedResult] is the JSON for the returned remote object.
644+
///
645+
/// Nested objects are not included in the result (they appear as `{}`),
646+
/// only primitive values, lists or maps, etc.
647+
///
648+
/// TODO(annagrin): Add recursive check for nested objects.
649+
Future<void> checkRuntime({
650+
required String breakpointId,
651+
required String expression,
652+
required dynamic expectedResult,
653+
}) async {
654+
return await _onBreakpoint(breakpointId, onPause: (event) async {
655+
var actual = await _evaluateJsExpression(event, expression);
656+
expect(actual.json, expectedResult);
657+
});
658+
}
659+
660+
/// Evaluates a dart [expression] on a breakpoint and validates result.
661+
///
662+
/// [breakpointId] is the ID of the breakpoint from the source.
663+
/// [expression] is a dart expression.
664+
/// [expectedResult] is the JSON for the returned remote object.
665+
/// [expectedError] is the error string if the error is expected.
602666
Future<void> check(
603667
{required String breakpointId,
604668
required String expression,
@@ -609,45 +673,89 @@ class TestDriver {
609673

610674
var dartLine = _findBreakpointLine(breakpointId);
611675
return await _onBreakpoint(breakpointId, onPause: (event) async {
612-
// Retrieve the call frame and its scope variables.
613-
var frame = event.getCallFrames().first;
614-
var scope = await _collectScopeVariables(frame);
615-
616-
// Perform an incremental compile.
617-
var result = await compiler.compileExpression(
618-
input: input,
619-
line: dartLine,
620-
column: 1,
621-
scope: scope,
622-
expression: expression);
623-
624-
if (expectedError != null) {
676+
var evalResult = await _evaluateDartExpression(
677+
event,
678+
expression,
679+
dartLine,
680+
);
681+
682+
var error = evalResult.json['error'];
683+
if (error != null) {
625684
expect(
626-
result,
627-
const TypeMatcher<TestCompilationResult>().having(
628-
(_) => result.result, 'result', _matches(expectedError)));
629-
setup.diagnosticMessages.clear();
630-
setup.errors.clear();
631-
return;
685+
expectedError,
686+
isNotNull,
687+
reason: 'Unexpected expression evaluation failure:\n$error',
688+
);
689+
expect(error, _matches(expectedError!));
690+
} else {
691+
var actual = await stringifyRemoteObject(evalResult);
692+
expect(actual, _matches(expectedResult!));
632693
}
694+
});
695+
}
633696

634-
if (!result.isSuccess) {
635-
throw Exception(
636-
'Unexpected expression evaluation failure:\n${result.result}');
637-
}
697+
Future<wip.RemoteObject> _evaluateJsExpression(
698+
wip.DebuggerPausedEvent event,
699+
String expression, {
700+
bool returnByValue = true,
701+
}) async {
702+
var frame = event.getCallFrames().first;
703+
704+
var loadModule = setup.moduleFormat == ModuleFormat.amd
705+
? 'require'
706+
: 'dart_library.import';
707+
708+
var jsExpression = '''
709+
(function () {
710+
try {
711+
var sdk = $loadModule('dart_sdk');
712+
var dart = sdk.dart;
713+
var interceptors = sdk._interceptors;
714+
return $expression;
715+
} catch (error) {
716+
return "Runtime API call failed: " + error.name +
717+
": " + error.message + ": " + error.stack;
718+
}
719+
})()
720+
''';
721+
722+
return await debugger.evaluateOnCallFrame(
723+
frame.callFrameId,
724+
jsExpression,
725+
returnByValue: returnByValue,
726+
);
727+
}
638728

639-
// Evaluate the compiled expression.
640-
var evalResult = await debugger.evaluateOnCallFrame(
641-
frame.callFrameId, result.result!,
642-
returnByValue: false);
729+
Future<wip.RemoteObject> _evaluateDartExpression(
730+
wip.DebuggerPausedEvent event,
731+
String expression,
732+
int dartLine, {
733+
bool returnByValue = false,
734+
}) async {
735+
// Retrieve the call frame and its scope variables.
736+
var frame = event.getCallFrames().first;
737+
var scope = await _collectScopeVariables(frame);
643738

644-
var value = await stringifyRemoteObject(evalResult);
739+
// Perform an incremental compile.
740+
var result = await compiler.compileExpression(
741+
input: input,
742+
line: dartLine,
743+
column: 1,
744+
scope: scope,
745+
expression: expression);
746+
747+
if (!result.isSuccess) {
748+
setup.diagnosticMessages.clear();
749+
setup.errors.clear();
750+
return wip.RemoteObject({'error': result.result});
751+
}
645752

646-
expect(
647-
result,
648-
const TypeMatcher<TestCompilationResult>()
649-
.having((_) => value, 'result', _matches(expectedResult!)));
650-
});
753+
// Evaluate the compiled expression.
754+
return await debugger.evaluateOnCallFrame(
755+
frame.callFrameId,
756+
result.result!,
757+
returnByValue: returnByValue,
758+
);
651759
}
652760

653761
/// Generate simple string representation of a RemoteObject that closely

0 commit comments

Comments
 (0)