Skip to content

Commit 773cf6b

Browse files
natebiggsCommit Queue
authored andcommitted
[ddc] Fix issues with duplicate library name aliases.
The attached bug shows an issue users have been encountering where a constructor seems to be undefined. It turns out this is because DDC is trying to read the constructor from the wrong library. This is happening because both 'package:dio' and a sister package 'package:dio_web_adapter' both contain a library with the same path: 'src/adapter.dart'. 'BrowserHttpClientAdapter' the class they are trying to reference is defined in 'package:dio/src/adapter.dart'. However, due to a naming collision, their import is referencing 'package:dio_web_adapter/src/adapter.dart'. This naming collision happens because of the logic in '_jsLibraryAlias'. By truncating the start of the import URI (i.e. 'dio/' and 'dio_web_adapter/') the two libraries map to the same alias. This alias is then used to as the key in the AMD module export object and since both libraries are in the same module, only the second one gets exported. This code may have been written with the assumption that libraries from different packages would always be in different modules (in which case the shortened paths shouldn't collide) but this is not the case. The fix is to use the full import URI including the package name. In writing the attached modular test I discovered another issue that only affects es6 imports. The ScopedId resolver was not considering NameSpecifier as a declaration point for variables. This lead to a similar name collision since the import alias's name was also being derived from a truncated import URI. In the test, both 'f1/foo.dart' and 'f2/foo.dart' were being imported 'as foo'. Now one is 'as foo' and the other is 'as foo$'. The first issue affects both AMD and es6 while the second issue only affects es6. The modular tests run with es6 so the new test fails if either of these fixes is not in place. The new DDC module system is not affected by either issue since it doesn't use NameSpecifiers and it uses the full import URI as a string to register libraries rather than a shortened alias. Tested on TGP and with a local Flutter application. Bug: #56498 Change-Id: I5bdb945cfbe615874b40e2fc4ebba31b661cf3b7 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/410260 Reviewed-by: Nicholas Shahan <[email protected]> Commit-Queue: Nate Biggs <[email protected]>
1 parent 997066e commit 773cf6b

File tree

8 files changed

+98
-25
lines changed

8 files changed

+98
-25
lines changed

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,13 +567,31 @@ bool isSdkInternalRuntimeUri(Uri importUri) {
567567
return importUri.isScheme('dart') && importUri.path == '_runtime';
568568
}
569569

570+
/// Returns a name that can be used to represent a library within the context
571+
/// of a module. This name is not globally unique and therefore should not be
572+
/// used as an import/export name for the library as this can lead to naming
573+
/// collisions. Use [libraryUriToImportName] to ensure global uniqueness.
574+
///
575+
/// The name should be given to a [ScopedId] to ensure there are no local
576+
/// collisions.
570577
String libraryUriToJsIdentifier(Uri importUri) {
571578
if (importUri.isScheme('dart')) {
572579
return isSdkInternalRuntimeUri(importUri) ? 'dart' : importUri.path;
573580
}
574581
return pathToJSIdentifier(p.withoutExtension(importUri.pathSegments.last));
575582
}
576583

584+
/// Returns a globally unique name that can be used to represent a library.
585+
/// Since this name is unique, it can safely be used for imports and exports
586+
/// to/from JS modules. If global uniqueness is not necessary, use
587+
/// [libraryUriToJsIdentifier] which produces shorter names.
588+
String libraryUriToImportName(Uri importUri) {
589+
if (importUri.isScheme('dart')) {
590+
return isSdkInternalRuntimeUri(importUri) ? 'dart' : importUri.path;
591+
}
592+
return pathToJSIdentifier(p.withoutExtension(importUri.path));
593+
}
594+
577595
/// Creates function name given [moduleName].
578596
String loadFunctionName(String moduleName) =>
579597
'load__${pathToJSIdentifier(moduleName.replaceAll('.', '_'))}';

pkg/dev_compiler/lib/src/js_ast/printer.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1760,6 +1760,8 @@ class MinifyRenamer implements LocalNamer {
17601760
/// Like [BaseVisitor], but calls [declare] for [Identifier] declarations, and
17611761
/// [visitIdentifier] otherwise.
17621762
abstract class VariableDeclarationVisitor extends BaseVisitorVoid {
1763+
bool _inImportDeclaration = false;
1764+
17631765
void declare(Identifier node);
17641766

17651767
@override
@@ -1827,4 +1829,29 @@ abstract class VariableDeclarationVisitor extends BaseVisitorVoid {
18271829
element.accept(this);
18281830
}
18291831
}
1832+
1833+
@override
1834+
void visitImportDeclaration(ImportDeclaration node) {
1835+
if (node.defaultBinding != null) {
1836+
declare(node.defaultBinding!);
1837+
}
1838+
if (node.namedImports != null) {
1839+
_inImportDeclaration = true;
1840+
for (var namedImport in node.namedImports!) {
1841+
namedImport.accept(this);
1842+
}
1843+
_inImportDeclaration = false;
1844+
}
1845+
}
1846+
1847+
@override
1848+
void visitNameSpecifier(NameSpecifier node) {
1849+
final asName = node.asName;
1850+
// The specified 'as' name only declares a local name in the context of an
1851+
// import.
1852+
if (_inImportDeclaration && asName != null) {
1853+
declare(asName);
1854+
}
1855+
node.name?.accept(this);
1856+
}
18301857
}

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

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,16 @@ import 'package:kernel/library_index.dart';
1919
import 'package:kernel/src/dart_type_equivalence.dart';
2020
import 'package:kernel/type_algebra.dart';
2121
import 'package:kernel/type_environment.dart';
22-
import 'package:path/path.dart' as p;
2322
import 'package:source_span/source_span.dart' show SourceLocation;
2423

2524
import '../command/options.dart' show Options;
2625
import '../compiler/js_names.dart' as js_ast;
2726
import '../compiler/js_utils.dart' as js_ast;
2827
import '../compiler/module_builder.dart'
29-
show isSdkInternalRuntimeUri, libraryUriToJsIdentifier;
28+
show
29+
isSdkInternalRuntimeUri,
30+
libraryUriToImportName,
31+
libraryUriToJsIdentifier;
3032
import '../compiler/module_containers.dart' show ModuleItemContainer;
3133
import '../compiler/rewrite_async.dart';
3234
import '../js_ast/js_ast.dart' as js_ast;
@@ -818,11 +820,6 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
818820
return module;
819821
}
820822

821-
/// Choose a canonical name from the [library] element.
822-
String _jsLibraryName(Library library) {
823-
return libraryUriToJsIdentifier(library.importUri);
824-
}
825-
826823
/// Choose a module-unique name from the [library] element.
827824
///
828825
/// Returns null if no alias exists or there are multiple output paths
@@ -834,17 +831,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
834831
var uri = library.importUri.normalizePath();
835832
if (uri.isScheme('dart')) return null;
836833

837-
Iterable<String> segments;
838-
if (uri.isScheme('package')) {
839-
// Strip the package name.
840-
segments = uri.pathSegments.skip(1);
841-
} else {
842-
segments = uri.pathSegments;
843-
}
844-
845-
var qualifiedPath =
846-
js_ast.pathToJSIdentifier(p.withoutExtension(segments.join('/')));
847-
return qualifiedPath == _jsLibraryName(library) ? null : qualifiedPath;
834+
return libraryUriToImportName(uri);
848835
}
849836

850837
/// Debugger friendly name for a Dart [library].
@@ -7751,7 +7738,8 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
77517738

77527739
void _setEmitIfIncrementalLibrary(Library library) {
77537740
if (_incrementalMode) {
7754-
_setEmitIfIncremental(_libraryToModule(library), _jsLibraryName(library));
7741+
_setEmitIfIncremental(_libraryToModule(library),
7742+
libraryUriToJsIdentifier(library.importUri));
77557743
}
77567744
}
77577745

@@ -7994,7 +7982,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
79947982
}
79957983
var libraryId = _isBuildingSdk && _isDartLibrary(library, '_rti')
79967984
? _rtiLibraryId
7997-
: js_ast.ScopedId(_jsLibraryName(library));
7985+
: js_ast.ScopedId(libraryUriToJsIdentifier(library.importUri));
79987986

79997987
_libraries[library] = libraryId;
80007988
var alias = _jsLibraryAlias(library);
@@ -8048,8 +8036,8 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
80488036

80498037
// It's either one of the libraries in this module, or it's an import.
80508038
return _libraries[library] ??
8051-
_imports.putIfAbsent(
8052-
library, () => js_ast.ScopedId(_jsLibraryName(library)));
8039+
_imports.putIfAbsent(library,
8040+
() => js_ast.ScopedId(libraryUriToJsIdentifier(library.importUri)));
80538041
}
80548042

80558043
/// Emits imports into [items].
@@ -8080,7 +8068,8 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
80808068
var imports = <js_ast.NameSpecifier>[];
80818069
for (var library in libraries) {
80828070
if (!_incrementalMode ||
8083-
usedLibraries!.contains(_jsLibraryName(library))) {
8071+
usedLibraries!
8072+
.contains(libraryUriToJsIdentifier(library.importUri))) {
80848073
var alias = _jsLibraryAlias(library);
80858074
if (alias != null) {
80868075
var aliasId = js_ast.ScopedId(alias);
@@ -8180,7 +8169,8 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
81808169

81818170
if (usedLibraries.isNotEmpty) {
81828171
_libraries.forEach((library, libraryId) {
8183-
if (usedLibraries.contains(_jsLibraryName(library))) {
8172+
if (usedLibraries
8173+
.contains(libraryUriToJsIdentifier(library.importUri))) {
81848174
var alias = _jsLibraryAlias(library);
81858175
var aliasId = alias == null ? libraryId : js_ast.ScopedId(alias);
81868176
var asName = alias == null ? null : libraryId;

pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_suite.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,8 @@ class ExpressionEvaluationTestDriver {
171171
htmlBootstrapper = testDir.uri.resolve('bootstrapper.html');
172172
var bootstrapFile = File(htmlBootstrapper.toFilePath())..createSync();
173173
var moduleName = compiler.metadata!.name;
174-
var mainLibraryName = compiler.metadataForLibraryUri(input).name;
174+
var mainLibraryName = libraryUriToImportName(
175+
Uri.parse(compiler.metadataForLibraryUri(input).importUri));
175176
var appName = p.relative(
176177
p.withoutExtension(compiler.metadataForLibraryUri(input).importUri));
177178

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
export 'package:f2/foo.dart';
6+
7+
class F1 {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
class F2 {}

tests/modular/issue56498/main.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
import 'package:f1/foo.dart';
5+
6+
main() {
7+
print(F1());
8+
print(F2());
9+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
# for details. All rights reserved. Use of this source code is governed by a
3+
# BSD-style license that can be found in the LICENSE file.
4+
#
5+
# Test ensuring that the modular compiler works properly with `package:`
6+
# imports. This test also ensures that the dart2js implementation of the modular
7+
# test pipeline works as intended. The test is not designed to cover any
8+
# compiler or language feature explicitly.
9+
dependencies:
10+
main: [f1, expect]
11+
f1: f2
12+
13+
packages:
14+
f0: .
15+
f1: f1
16+
f2: f2

0 commit comments

Comments
 (0)