[kernel/DDC] DDC shouldn't crash when trying to translate line/column to offset for expresion compilation

Follow-up to https://dart-review.googlesource.com/c/sdk/+/446042.

Change-Id: Icfbaa763a6d6089a74f62088602c07c478520741
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/446420
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Jens Johansen <jensj@google.com>
Reviewed-by: Nicholas Shahan <nshahan@google.com>
This commit is contained in:
Jens Johansen
2025-08-24 23:50:07 -07:00
committed by Commit Queue
parent 33697720bf
commit abfa152cfc
3 changed files with 126 additions and 2 deletions
@@ -353,7 +353,14 @@ class ExpressionCompiler {
// TODO(jensj): Eventually make the scriptUri required.
if (scriptFileUri != null) {
final offset = _component.getOffset(scriptFileUri, line, column);
final offset = _component.getOffsetNoThrow(scriptFileUri, line, column);
if (offset < 0) {
_log(
'Got offset negative offset ($offset) for '
'$scriptFileUri:$line:$column',
);
return DartScope(library, null, null, null, null, {}, []);
}
final scope = DartScopeBuilder2.findScopeFromOffset(
library,
scriptFileUri,
@@ -388,11 +395,19 @@ class ExpressionCompiler {
final foundScopes = <DartScope>[];
for (final fileUri in fileUris) {
final offset = _component.getOffset(fileUri, line, column);
final offset = _component.getOffsetNoThrow(fileUri, line, column);
if (offset < 0) continue;
foundScopes.add(
DartScopeBuilder2.findScopeFromOffset(library, fileUri, offset),
);
}
if (foundScopes.isEmpty) {
_log(
'Got only negative offsets for library/parts ${library.fileUri} '
'at $line:$column.',
);
return DartScope(library, null, null, null, null, {}, []);
}
if (foundScopes.length == 1) {
return foundScopes[0];
}
@@ -217,6 +217,88 @@ void runExpressionCompilationTests(ExpressionCompilerWorkerTestDriver driver) {
);
});
test('does not crash on line 0 in a library', () {
driver.requestController.add({
'command': 'UpdateDeps',
'inputs': driver.inputs,
});
driver.requestController.add({
'command': 'CompileExpression',
'expression': '1 + 1',
'line': 0,
'column': 0,
'jsModules': {},
'jsScope': {},
'libraryUri': driver.config.getModule('testModule').libraryUris.first,
'moduleName': driver.config.getModule('testModule').moduleName,
});
expect(
driver.responseController.stream,
emitsInOrder([
equals({'succeeded': true}),
equals({
'succeeded': true,
'errors': isEmpty,
'warnings': isEmpty,
'infos': isEmpty,
'compiledProcedure': contains('1 + 1;'),
}),
]),
);
});
test('does not crash on line thats too high in a library', () {
driver.requestController.add({
'command': 'UpdateDeps',
'inputs': driver.inputs,
});
driver.requestController.add({
'command': 'CompileExpression',
'expression': '1 + 1',
'line': 10000000,
'column': 1,
'jsModules': {},
'jsScope': {},
'libraryUri': driver.config.getModule('testModule').libraryUris.first,
'moduleName': driver.config.getModule('testModule').moduleName,
});
driver.requestController.add({
'command': 'CompileExpression',
'expression': '2 + 2',
'line': 1,
'column': 10000000,
'jsModules': {},
'jsScope': {},
'libraryUri': driver.config.getModule('testModule').libraryUris.first,
'moduleName': driver.config.getModule('testModule').moduleName,
});
expect(
driver.responseController.stream,
emitsInOrder([
equals({'succeeded': true}),
equals({
'succeeded': true,
'errors': isEmpty,
'warnings': isEmpty,
'infos': isEmpty,
'compiledProcedure': contains('1 + 1;'),
}),
equals({
'succeeded': true,
'errors': isEmpty,
'warnings': isEmpty,
'infos': isEmpty,
'compiledProcedure': contains('2 + 2;'),
}),
]),
);
});
test('can compile expressions in a library', () {
driver.requestController.add({
'command': 'UpdateDeps',
+27
View File
@@ -159,6 +159,14 @@ class Component extends TreeNode {
return uriToSource[file]?.getOffset(line, column) ?? -1;
}
/// Translates line and column numbers to an offset in the given file.
///
/// Returns offset of the line and column in the file, or -1 if the
/// source is not available, has no lines, or the line is out of range.
int getOffsetNoThrow(Uri file, int line, int column) {
return uriToSource[file]?.getOffsetNoThrow(line, column) ?? -1;
}
void addMetadataRepository(MetadataRepository repository) {
metadata[repository.tag] = repository;
}
@@ -291,6 +299,25 @@ class Source {
RangeError.checkValueInInterval(offset, 0, lineStarts.last, 'offset');
return offset;
}
/// Translates 1-based line and column numbers to an offset in the given file
///
/// Returns offset of the line and column in the file, or -1 if the source
/// has no lines or the input is out of range.
int getOffsetNoThrow(int line, int column) {
List<int>? lineStarts = this.lineStarts;
if (lineStarts == null || lineStarts.isEmpty) {
return -1;
}
if (line < 1 || line > lineStarts.length) {
return -1;
}
int offset = lineStarts[line - 1] + column - 1;
if (offset < 0 || offset > lineStarts.last) {
return -1;
}
return offset;
}
}
abstract class MetadataRepository<T> {