Skip to content

Commit f7b2b78

Browse files
wip: sanitizers
1 parent c15153b commit f7b2b78

File tree

4 files changed

+312
-10
lines changed

4 files changed

+312
-10
lines changed
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
// Copyright (c) 2015, 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+
@TestOn('vm')
6+
library;
7+
8+
import 'dart:io';
9+
10+
import 'package:path/path.dart' as p;
11+
import 'package:test/test.dart';
12+
import 'package:test_core/src/util/exit_codes.dart' as exit_codes;
13+
import 'package:test_descriptor/test_descriptor.dart' as d;
14+
15+
import '../io.dart';
16+
17+
void main() {
18+
setUpAll(precompileTestExecutable);
19+
20+
test('asan success', () async {
21+
final testSource = '''
22+
@TestOn('vm-asan')
23+
library asan_test;
24+
25+
import 'package:test/test.dart';
26+
27+
void main() {
28+
test('const', () {
29+
// I.e., correct during kernel compilation.
30+
expect(const bool.fromEnvironment("dart.vm.asan"), equals(true));
31+
32+
expect(const bool.fromEnvironment("dart.vm.msan"), equals(false));
33+
expect(const bool.fromEnvironment("dart.vm.tsan"), equals(false));
34+
});
35+
36+
test('new', () {
37+
// I.e., correct during VM lookup.
38+
expect(new bool.fromEnvironment("dart.vm.asan"), equals(true));
39+
40+
expect(new bool.fromEnvironment("dart.vm.msan"), equals(false));
41+
expect(new bool.fromEnvironment("dart.vm.tsan"), equals(false));
42+
});
43+
}
44+
''';
45+
46+
await d.file('test.dart', testSource).create();
47+
var test = await runTest(['test.dart', '-p', 'vm-asan']);
48+
49+
expect(test.stdout, emitsThrough(contains('+2: All tests passed!')));
50+
await test.shouldExit(0);
51+
});
52+
53+
test('asan failure', () async {
54+
final testSource = '''
55+
@TestOn('vm-asan')
56+
library asan_environment_test;
57+
58+
import 'package:test/test.dart';
59+
import 'dart:ffi';
60+
61+
@Native<Pointer Function(IntPtr)>(symbol: 'malloc')
62+
external Pointer malloc(int size);
63+
@Native<Void Function(Pointer)>(symbol: 'free')
64+
external void free(Pointer ptr);
65+
@Native<Void Function(Pointer, Int, Size)>(symbol: 'memset')
66+
external void memset(Pointer ptr, int char, int size);
67+
68+
void main() {
69+
test('use-after-free', () {
70+
var p = malloc(sizeOf<Long>()).cast<Long>();
71+
free(p);
72+
memset(p, 42, sizeOf<Long>()); // ASAN: heap-use-after-free
73+
});
74+
}
75+
''';
76+
77+
await d.file('test.dart', testSource).create();
78+
var test = await runTest(['test.dart', '-p', 'vm-asan']);
79+
80+
expect(
81+
test.stderr,
82+
emitsThrough(contains("AddressSanitizer: heap-use-after-free")),
83+
);
84+
await test.shouldExit(1);
85+
});
86+
87+
test('msan success', () async {
88+
final testSource = '''
89+
@TestOn('vm-msan')
90+
library msan_environment_test;
91+
92+
import 'package:test/test.dart';
93+
94+
void main() {
95+
test('const', () {
96+
// I.e., correct during kernel compilation.
97+
expect(const bool.fromEnvironment("dart.vm.msan"), equals(true));
98+
99+
expect(const bool.fromEnvironment("dart.vm.asan"), equals(false));
100+
expect(const bool.fromEnvironment("dart.vm.tsan"), equals(false));
101+
});
102+
103+
test('new', () {
104+
// I.e., correct during VM lookup.
105+
expect(new bool.fromEnvironment("dart.vm.msan"), equals(true));
106+
107+
expect(new bool.fromEnvironment("dart.vm.asan"), equals(false));
108+
expect(new bool.fromEnvironment("dart.vm.tsan"), equals(false));
109+
});
110+
}
111+
''';
112+
113+
await d.file('test.dart', testSource).create();
114+
var test = await runTest(['test.dart', '-p', 'vm-msan']);
115+
116+
expect(test.stdout, emitsThrough(contains('+2: All tests passed!')));
117+
await test.shouldExit(0);
118+
});
119+
120+
test('msan failure', () async {
121+
final testSource = '''
122+
@TestOn('vm-msan')
123+
library msan_test;
124+
125+
import 'dart:ffi';
126+
import 'package:test/test.dart';
127+
128+
@Native<Pointer Function(IntPtr)>(symbol: 'malloc')
129+
external Pointer malloc(int size);
130+
@Native<Void Function(Pointer)>(symbol: 'free')
131+
external void free(Pointer ptr);
132+
@Native<Void Function(Pointer, Pointer, Size)>(symbol: 'memcmp')
133+
external void memcmp(Pointer a, Pointer b, int size);
134+
135+
void main() {
136+
test('uninitialized', () {
137+
var a = malloc(8);
138+
var b = malloc(8);
139+
memcmp(a, b, 8); // MSAN: use-of-uninitialized-value
140+
free(b);
141+
free(a);
142+
});
143+
}
144+
''';
145+
146+
await d.file('test.dart', testSource).create();
147+
var test = await runTest(['test.dart', '-p', 'vm-msan']);
148+
149+
expect(
150+
test.stderr,
151+
emitsThrough(contains("MemorySanitizer: use-of-uninitialized-value")),
152+
);
153+
await test.shouldExit(1);
154+
});
155+
156+
test('tsan success', () async {
157+
final testSource = '''
158+
@TestOn('vm-tsan')
159+
library tsan_environment_test;
160+
161+
import 'package:test/test.dart';
162+
163+
void main() {
164+
test('const', () {
165+
// I.e., correct during kernel compilation.
166+
expect(const bool.fromEnvironment("dart.vm.tsan"), equals(true));
167+
168+
expect(const bool.fromEnvironment("dart.vm.asan"), equals(false));
169+
expect(const bool.fromEnvironment("dart.vm.msan"), equals(false));
170+
});
171+
172+
test('new', () {
173+
// I.e., correct during VM lookup.
174+
expect(new bool.fromEnvironment("dart.vm.tsan"), equals(true));
175+
176+
expect(new bool.fromEnvironment("dart.vm.asan"), equals(false));
177+
expect(new bool.fromEnvironment("dart.vm.msan"), equals(false));
178+
});
179+
}
180+
''';
181+
182+
await d.file('test.dart', testSource).create();
183+
var test = await runTest(['test.dart', '-p', 'vm-tsan']);
184+
185+
expect(test.stdout, emitsThrough(contains('+2: All tests passed!')));
186+
await test.shouldExit(0);
187+
});
188+
189+
test('tsan failure', () async {
190+
final testSource = '''
191+
@TestOn('vm-tsan')
192+
library tsan_test;
193+
194+
import 'dart:ffi';
195+
import 'dart:isolate';
196+
import 'package:test/test.dart';
197+
198+
@Native<Pointer Function(IntPtr)>(symbol: 'malloc')
199+
external Pointer malloc(int size);
200+
@Native<Void Function(Pointer)>(symbol: 'free')
201+
external void free(Pointer ptr);
202+
@Native<Void Function(Pointer, Int, Size)>(symbol: 'memset', isLeaf: true)
203+
external void memset_leaf(Pointer ptr, int char, int size);
204+
@Native<Void Function(IntPtr)>(symbol: 'usleep', isLeaf: true)
205+
external void usleep_leaf(int useconds);
206+
207+
child(addr) {
208+
var p = Pointer<IntPtr>.fromAddress(addr);
209+
for (var i = 0; i < 50000; i++) {
210+
memset_leaf(p, 42, sizeOf<IntPtr>()); // TSAN: data race
211+
usleep_leaf(100);
212+
}
213+
}
214+
215+
void main() {
216+
test('data race', () async {
217+
var p = malloc(sizeOf<IntPtr>()).cast<IntPtr>();
218+
var f = Isolate.run(() => child(p.address));
219+
220+
for (var i = 0; i < 50000; i++) {
221+
p[0] = p[0] + 1; // TSAN: data race
222+
usleep_leaf(100);
223+
}
224+
225+
await f;
226+
free(p);
227+
});
228+
}
229+
''';
230+
231+
await d.file('test.dart', testSource).create();
232+
var test = await runTest(['test.dart', '-p', 'vm-tsan']);
233+
234+
expect(test.stderr, emitsThrough(contains("ThreadSanitizer: data race")));
235+
await test.shouldExit(0);
236+
});
237+
}

pkgs/test_api/lib/src/backend/runtime.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,27 @@ final class Runtime {
1515
Compiler.source,
1616
Compiler.exe,
1717
], isDartVM: true);
18+
static const Runtime vmAsan = Runtime(
19+
'VM with Address Sanitizer',
20+
'vm-asan',
21+
Compiler.exe,
22+
[Compiler.exe],
23+
isDartVM: true,
24+
);
25+
static const Runtime vmMsan = Runtime(
26+
'VM with Memory Sanitizer',
27+
'vm-msan',
28+
Compiler.exe,
29+
[Compiler.exe],
30+
isDartVM: true,
31+
);
32+
static const Runtime vmTsan = Runtime(
33+
'VM with Thread Sanitizer',
34+
'vm-tsan',
35+
Compiler.exe,
36+
[Compiler.exe],
37+
isDartVM: true,
38+
);
1839

1940
/// Google Chrome.
2041
static const Runtime chrome = Runtime(
@@ -69,6 +90,9 @@ final class Runtime {
6990
/// The platforms that are supported by the test runner by default.
7091
static const List<Runtime> builtIn = [
7192
Runtime.vm,
93+
Runtime.vmAsan,
94+
Runtime.vmMsan,
95+
Runtime.vmTsan,
7296
Runtime.chrome,
7397
Runtime.firefox,
7498
Runtime.safari,

pkgs/test_core/lib/src/runner/loader.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'dart:async';
6+
import 'dart:ffi';
67
import 'dart:io';
78

89
import 'package:async/async.dart';
@@ -65,7 +66,12 @@ class Loader {
6566
/// Creates a new loader that loads tests on platforms defined in
6667
/// [Configuration.current].
6768
Loader() {
68-
_registerPlatformPlugin([Runtime.vm], VMPlatform.new);
69+
_registerPlatformPlugin([
70+
Runtime.vm,
71+
if (File('$sdkDir/bin/dartaotruntime_asan').existsSync()) Runtime.vmAsan,
72+
if (File('$sdkDir/bin/dartaotruntime_msan').existsSync()) Runtime.vmMsan,
73+
if (File('$sdkDir/bin/dartaotruntime_tsan').existsSync()) Runtime.vmTsan,
74+
], VMPlatform.new);
6975

7076
platformCallbacks.forEach((runtime, plugin) {
7177
_registerPlatformPlugin([runtime], plugin);

pkgs/test_core/lib/src/runner/vm/platform.dart

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class VMPlatform extends PlatformPlugin {
6262
Process process;
6363
try {
6464
process = await _spawnExecutable(
65+
platform,
6566
path,
6667
suiteConfig.metadata,
6768
serverSocket,
@@ -70,8 +71,12 @@ class VMPlatform extends PlatformPlugin {
7071
unawaited(serverSocket.close());
7172
rethrow;
7273
}
73-
process.stdout.listen(stdout.add);
74-
process.stderr.listen(stderr.add);
74+
process.exitCode.then((exitCode) async {
75+
if (exitCode != 0 && exitCode != -15) {
76+
// At least don't hang.
77+
exit(exitCode);
78+
}
79+
});
7580
var socket = await serverSocket.first;
7681
outerChannel = MultiChannel<Object?>(jsonSocketStreamChannel(socket));
7782
cleanupCallbacks
@@ -186,11 +191,32 @@ class VMPlatform extends PlatformPlugin {
186191
() => Future.wait([_compiler.dispose(), _tempDir.deleteWithRetry()]),
187192
);
188193

194+
String _aotRuntimeFor(SuitePlatform platform) {
195+
var sanSuffix = '';
196+
switch (platform.runtime) {
197+
case Runtime.vmAsan:
198+
sanSuffix = '_asan';
199+
break;
200+
case Runtime.vmMsan:
201+
sanSuffix = '_msan';
202+
break;
203+
case Runtime.vmTsan:
204+
sanSuffix = '_tsan';
205+
break;
206+
}
207+
var exeSuffix = Platform.isWindows ? '.exe' : '';
208+
return p.join(
209+
p.dirname(Platform.resolvedExecutable),
210+
'dartaotruntime$sanSuffix$exeSuffix',
211+
);
212+
}
213+
189214
/// Compiles [path] to a native executable and spawns it as a process.
190215
///
191216
/// Sets up a communication channel as well by passing command line arguments
192217
/// for the host and port of [socket].
193218
Future<Process> _spawnExecutable(
219+
SuitePlatform platform,
194220
String path,
195221
Metadata suiteMetadata,
196222
ServerSocket socket,
@@ -200,29 +226,38 @@ class VMPlatform extends PlatformPlugin {
200226
'Precompiled native executable tests are not supported at this time',
201227
);
202228
}
203-
var executable = await _compileToNative(path, suiteMetadata);
204-
return await Process.start(executable, [
229+
230+
var sharedLibrary = await _compileToNative(platform, path, suiteMetadata);
231+
return await Process.start(_aotRuntimeFor(platform), [
232+
sharedLibrary,
205233
socket.address.host,
206234
socket.port.toString(),
207-
]);
235+
], mode: ProcessStartMode.inheritStdio);
208236
}
209237

210-
/// Compiles [path] to a native executable using `dart compile exe`.
211-
Future<String> _compileToNative(String path, Metadata suiteMetadata) async {
238+
/// Compiles [path] to a native shared library using `dart compile aot-snapshot`.
239+
Future<String> _compileToNative(
240+
SuitePlatform platform,
241+
String path,
242+
Metadata suiteMetadata,
243+
) async {
212244
var bootstrapPath = await _bootstrapNativeTestFile(
213245
path,
214246
suiteMetadata.languageVersionComment ??
215247
await rootPackageLanguageVersionComment,
216248
);
217-
var output = File(p.setExtension(bootstrapPath, '.exe'));
249+
var output = File(p.setExtension(bootstrapPath, '.so'));
218250
var processResult = await Process.run(Platform.resolvedExecutable, [
219251
'compile',
220-
'exe',
252+
'aot-snapshot',
221253
bootstrapPath,
222254
'--output',
223255
output.path,
224256
'--packages',
225257
(await packageConfigUri).toFilePath(),
258+
if (platform.runtime == Runtime.vmAsan) '--target-sanitizer=asan',
259+
if (platform.runtime == Runtime.vmMsan) '--target-sanitizer=msan',
260+
if (platform.runtime == Runtime.vmTsan) '--target-sanitizer=tsan',
226261
]);
227262
if (processResult.exitCode != 0 || !(await output.exists())) {
228263
throw LoadException(path, '''

0 commit comments

Comments
 (0)