Skip to content

Commit 9b2c5ce

Browse files
committed
Add SdkPatcher and implement top-level function / getter / setter patching.
[email protected], [email protected] BUG= Review URL: https://codereview.chromium.org/2411883003 .
1 parent 12b1846 commit 9b2c5ce

File tree

4 files changed

+332
-0
lines changed

4 files changed

+332
-0
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// Copyright (c) 2016, 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+
library analyzer.src.dart.sdk.patch;
6+
7+
import 'package:analyzer/dart/ast/ast.dart';
8+
import 'package:analyzer/dart/ast/token.dart';
9+
import 'package:analyzer/error/listener.dart';
10+
import 'package:analyzer/file_system/file_system.dart';
11+
import 'package:analyzer/src/dart/scanner/reader.dart';
12+
import 'package:analyzer/src/dart/scanner/scanner.dart';
13+
import 'package:analyzer/src/dart/sdk/sdk.dart';
14+
import 'package:analyzer/src/generated/parser.dart';
15+
import 'package:analyzer/src/generated/sdk.dart';
16+
import 'package:analyzer/src/generated/source.dart';
17+
import 'package:meta/meta.dart';
18+
import 'package:path/src/context.dart';
19+
20+
/**
21+
* [SdkPatcher] applies patches to SDK [CompilationUnit].
22+
*/
23+
class SdkPatcher {
24+
/**
25+
* Patch the given [unit] of a SDK [source] with the patches defined in
26+
* the [sdk] for the given [platform]. Throw [ArgumentError] if a patch
27+
* file cannot be read, or the contents violates rules for patch files.
28+
*
29+
* If [addNewTopLevelDeclarations] is `true`, then the [unit] is the
30+
* defining unit of a library, so new top-level declarations should be
31+
* added to this unit. For parts new declarations may be added only to the
32+
* patched classes.
33+
*
34+
* TODO(scheglov) auto-detect [addNewTopLevelDeclarations]
35+
*/
36+
void patch(FolderBasedDartSdk sdk, int platform,
37+
AnalysisErrorListener errorListener, Source source, CompilationUnit unit,
38+
{bool addNewTopLevelDeclarations: true}) {
39+
// Prepare the patch files to apply.
40+
List<String> patchPaths;
41+
{
42+
// TODO(scheglov) add support for patching parts
43+
String uriStr = source.uri.toString();
44+
SdkLibrary sdkLibrary = sdk.getSdkLibrary(uriStr);
45+
if (sdkLibrary == null) {
46+
throw new ArgumentError(
47+
'The library $uriStr is not defined in the SDK.');
48+
}
49+
patchPaths = sdkLibrary.getPatches(platform);
50+
}
51+
52+
bool strongMode = sdk.analysisOptions.strongMode;
53+
Context pathContext = sdk.resourceProvider.pathContext;
54+
for (String path in patchPaths) {
55+
String pathInLib = pathContext.joinAll(path.split('/'));
56+
File patchFile = sdk.libraryDirectory.getChildAssumingFile(pathInLib);
57+
if (!patchFile.exists) {
58+
throw new ArgumentError(
59+
'The patch file ${patchFile.path} does not exist.');
60+
}
61+
Source patchSource = patchFile.createSource();
62+
CompilationUnit patchUnit = parse(patchSource, strongMode, errorListener);
63+
_patchTopLevelDeclarations(
64+
source, unit, patchSource, patchUnit, addNewTopLevelDeclarations);
65+
}
66+
}
67+
68+
void _failExternalKeyword(Source source, String name, int offset) {
69+
throw new ArgumentError(
70+
'The keyword "external" was expected for "$name" in $source @ $offset.');
71+
}
72+
73+
void _patchTopLevelDeclarations(
74+
Source baseSource,
75+
CompilationUnit baseUnit,
76+
Source patchSource,
77+
CompilationUnit patchUnit,
78+
bool addNewTopLevelDeclarations) {
79+
List<CompilationUnitMember> declarationsToAppend = [];
80+
for (CompilationUnitMember patchDeclaration in patchUnit.declarations) {
81+
if (patchDeclaration is FunctionDeclaration) {
82+
String name = patchDeclaration.name.name;
83+
if (_hasPatchAnnotation(patchDeclaration.metadata)) {
84+
for (CompilationUnitMember baseDeclaration in baseUnit.declarations) {
85+
if (patchDeclaration is FunctionDeclaration &&
86+
baseDeclaration is FunctionDeclaration &&
87+
baseDeclaration.name.name == name) {
88+
if (_hasPatchAnnotation(patchDeclaration.metadata)) {
89+
// Remove the "external" keyword.
90+
if (baseDeclaration.externalKeyword != null) {
91+
baseDeclaration.externalKeyword = null;
92+
} else {
93+
_failExternalKeyword(
94+
baseSource, name, baseDeclaration.offset);
95+
}
96+
// Replace the body.
97+
baseDeclaration.functionExpression.body =
98+
patchDeclaration.functionExpression.body;
99+
}
100+
}
101+
}
102+
} else if (addNewTopLevelDeclarations) {
103+
// No @patch, must be private.
104+
if (!Identifier.isPrivateName(name)) {
105+
throw new ArgumentError(
106+
'The patch file $patchSource attempts to append '
107+
'a non-private declaration "$name".');
108+
}
109+
declarationsToAppend.add(patchDeclaration);
110+
}
111+
}
112+
}
113+
// Append new top-level declarations.
114+
baseUnit.declarations.addAll(declarationsToAppend);
115+
}
116+
117+
/**
118+
* Parse the given [source] into AST.
119+
*/
120+
@visibleForTesting
121+
static CompilationUnit parse(
122+
Source source, bool strong, AnalysisErrorListener errorListener) {
123+
String code = source.contents.data;
124+
125+
CharSequenceReader reader = new CharSequenceReader(code);
126+
Scanner scanner = new Scanner(source, reader, errorListener);
127+
scanner.scanGenericMethodComments = strong;
128+
Token token = scanner.tokenize();
129+
LineInfo lineInfo = new LineInfo(scanner.lineStarts);
130+
131+
Parser parser = new Parser(source, errorListener);
132+
parser.parseGenericMethodComments = strong;
133+
CompilationUnit unit = parser.parseCompilationUnit(token);
134+
unit.lineInfo = lineInfo;
135+
return unit;
136+
}
137+
138+
/**
139+
* Return `true` if [metadata] has the `@patch` annotation.
140+
*/
141+
static bool _hasPatchAnnotation(List<Annotation> metadata) {
142+
return metadata.any((annotation) {
143+
Identifier name = annotation.name;
144+
return annotation.constructorName == null &&
145+
name is SimpleIdentifier &&
146+
name.name == 'patch';
147+
});
148+
}
149+
}

pkg/analyzer/lib/src/dart/sdk/sdk.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ abstract class AbstractDartSdk implements DartSdk {
6565

6666
PackageBundle _sdkBundle;
6767

68+
/**
69+
* Return the analysis options for this SDK analysis context.
70+
*/
71+
AnalysisOptions get analysisOptions => _analysisOptions;
72+
6873
/**
6974
* Set the [options] for this SDK analysis context. Throw [StateError] if the
7075
* context has been already created.
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// Copyright (c) 2016, 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+
import 'package:analyzer/dart/ast/ast.dart';
6+
import 'package:analyzer/file_system/file_system.dart';
7+
import 'package:analyzer/file_system/memory_file_system.dart';
8+
import 'package:analyzer/src/dart/sdk/patch.dart';
9+
import 'package:analyzer/src/dart/sdk/sdk.dart';
10+
import 'package:analyzer/src/generated/engine.dart';
11+
import 'package:analyzer/src/generated/sdk.dart';
12+
import 'package:analyzer/src/generated/source.dart';
13+
import 'package:analyzer/src/util/fast_uri.dart';
14+
import 'package:test/test.dart';
15+
import 'package:test_reflective_loader/test_reflective_loader.dart';
16+
17+
main() {
18+
defineReflectiveSuite(() {
19+
defineReflectiveTests(SdkPatcherTest);
20+
});
21+
}
22+
23+
@reflectiveTest
24+
class SdkPatcherTest {
25+
MemoryResourceProvider provider = new MemoryResourceProvider();
26+
Folder sdkFolder;
27+
FolderBasedDartSdk sdk;
28+
29+
SdkPatcher patcher = new SdkPatcher();
30+
RecordingErrorListener listener = new RecordingErrorListener();
31+
32+
void setUp() {
33+
sdkFolder = provider.getFolder(_p('/sdk'));
34+
}
35+
36+
test_fail_noSuchLibrary() {
37+
expect(() {
38+
_setSdkLibraries('const LIBRARIES = const {};');
39+
_createSdk();
40+
File file = provider.newFile(_p('/sdk/lib/test/test.dart'), '');
41+
Source source = file.createSource(FastUri.parse('dart:test'));
42+
CompilationUnit unit = SdkPatcher.parse(source, true, listener);
43+
patcher.patch(sdk, SdkLibraryImpl.VM_PLATFORM, listener, source, unit);
44+
}, throwsArgumentError);
45+
}
46+
47+
test_fail_patchFileDoesNotExist() {
48+
expect(() {
49+
_setSdkLibraries(r'''
50+
final Map<String, LibraryInfo> LIBRARIES = const <String, LibraryInfo> {
51+
'test' : const LibraryInfo(
52+
'test/test.dart',
53+
patches: {VM_PLATFORM: ['does_not_exists.dart']}),
54+
};''');
55+
_createSdk();
56+
File file = provider.newFile(_p('/sdk/lib/test/test.dart'), '');
57+
Source source = file.createSource(FastUri.parse('dart:test'));
58+
CompilationUnit unit = SdkPatcher.parse(source, true, listener);
59+
patcher.patch(sdk, SdkLibraryImpl.VM_PLATFORM, listener, source, unit);
60+
}, throwsArgumentError);
61+
}
62+
63+
test_topLevel_append() {
64+
CompilationUnit unit = _doTopLevelPatching(
65+
r'''
66+
int bar() => 2;
67+
''',
68+
r'''
69+
int _foo1() => 1;
70+
int get _foo2 => 1;
71+
void set _foo3(int val) {}
72+
''');
73+
_assertUnitCode(
74+
unit,
75+
'int bar() => 2; int _foo1() => 1; '
76+
'int get _foo2 => 1; void set _foo3(int val) {}');
77+
}
78+
79+
test_topLevel_fail_append_notPrivate() {
80+
expect(() {
81+
_doTopLevelPatching(
82+
r'''
83+
int foo() => 1;
84+
''',
85+
r'''
86+
int bar() => 2;
87+
''');
88+
}, throwsArgumentError);
89+
}
90+
91+
test_topLevel_fail_noExternalKeyword() {
92+
expect(() {
93+
_doTopLevelPatching(
94+
r'''
95+
int foo();
96+
''',
97+
r'''
98+
@patch
99+
int foo() => 1;
100+
''');
101+
}, throwsArgumentError);
102+
}
103+
104+
test_topLevel_patch_function() {
105+
CompilationUnit unit = _doTopLevelPatching(
106+
r'''
107+
external int foo();
108+
int bar() => 2;
109+
''',
110+
r'''
111+
@patch
112+
int foo() => 1;
113+
''');
114+
_assertUnitCode(unit, 'int foo() => 1; int bar() => 2;');
115+
}
116+
117+
test_topLevel_patch_getter() {
118+
CompilationUnit unit = _doTopLevelPatching(
119+
r'''
120+
external int get foo;
121+
int bar() => 2;
122+
''',
123+
r'''
124+
@patch
125+
int get foo => 1;
126+
''');
127+
_assertUnitCode(unit, 'int get foo => 1; int bar() => 2;');
128+
}
129+
130+
test_topLevel_patch_setter() {
131+
CompilationUnit unit = _doTopLevelPatching(
132+
r'''
133+
external void set foo(int val);
134+
int bar() => 2;
135+
''',
136+
r'''
137+
@patch
138+
void set foo(int val) {}
139+
''');
140+
_assertUnitCode(unit, 'void set foo(int val) {} int bar() => 2;');
141+
}
142+
143+
void _assertUnitCode(CompilationUnit unit, String expectedCode) {
144+
expect(unit.toSource(), expectedCode);
145+
}
146+
147+
void _createSdk() {
148+
sdk = new FolderBasedDartSdk(provider, sdkFolder);
149+
sdk.analysisOptions = new AnalysisOptionsImpl()..strongMode = true;
150+
}
151+
152+
CompilationUnit _doTopLevelPatching(String baseCode, String patchCode) {
153+
_setSdkLibraries(r'''
154+
final Map<String, LibraryInfo> LIBRARIES = const <String, LibraryInfo> {
155+
'test' : const LibraryInfo(
156+
'test/test.dart',
157+
patches: {VM_PLATFORM: ['test/test_patch.dart']}),
158+
};''');
159+
File file = provider.newFile(_p('/sdk/lib/test/test.dart'), baseCode);
160+
provider.newFile(_p('/sdk/lib/test/test_patch.dart'), patchCode);
161+
162+
_createSdk();
163+
164+
Source source = file.createSource(FastUri.parse('dart:test'));
165+
CompilationUnit unit = SdkPatcher.parse(source, true, listener);
166+
patcher.patch(sdk, SdkLibraryImpl.VM_PLATFORM, listener, source, unit);
167+
return unit;
168+
}
169+
170+
String _p(String path) => provider.convertPath(path);
171+
172+
void _setSdkLibraries(String code) {
173+
provider.newFile(
174+
_p('/sdk/lib/_internal/sdk_library_metadata/lib/libraries.dart'), code);
175+
}
176+
}

pkg/analyzer/test/src/dart/sdk/test_all.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ library analyzer.test.src.dart.sdk.test_all;
66

77
import 'package:test_reflective_loader/test_reflective_loader.dart';
88

9+
import 'patch_test.dart' as patch_test;
910
import 'sdk_test.dart' as sdk;
1011

1112
/// Utility for manually running all tests.
1213
main() {
1314
defineReflectiveSuite(() {
15+
patch_test.main();
1416
sdk.main();
1517
}, name: 'sdk');
1618
}

0 commit comments

Comments
 (0)