Skip to content

Commit e605324

Browse files
rmacnak-googleCommit Queue
authored andcommitted
[vm, compiler] Add TSAN instrumentation to Dart field access.
Allows TSAN to detect data races involving Dart fields. TEST=tsan Cq-Include-Trybots: luci.dart.try:vm-tsan-linux-release-x64-try,vm-tsan-linux-release-arm64-try,iso-stress-linux-arm64-try,iso-stress-linux-x64-try Change-Id: Ic7a6c7e6c1810adf79b41e5c0ae891132f368a61 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/439143 Reviewed-by: Alexander Aprelev <[email protected]> Commit-Queue: Ryan Macnak <[email protected]>
1 parent f1a6d1c commit e605324

32 files changed

+949
-326
lines changed

runtime/docs/pragmas.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ These pragmas are part of the VM's API and are safe for use in external code.
2121
| `vm:awaiter-link` | [Specifying variable to follow for awaiter stack unwinding](awaiter_stack_traces.md) |
2222
| `vm:deeply-immutable` | [Specifying a class and all its subtypes are deeply immutable](deeply_immutable.md) |
2323
| `vm:align-loops` | Tells compiler to align all loop headers inside the function to an architecture specific boundary: currently 32 bytes on X64 and ARM64 (except Apple Silicon, which explicitly discourages aligning branch targets) |
24+
| `vm:no-sanitize-thread` | Disable ThreadSanitizer instrumentation |
2425

2526
## Unsafe pragmas for general use
2627

runtime/platform/thread_sanitizer.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,24 @@
1717

1818
#if defined(USING_THREAD_SANITIZER)
1919
#define NO_SANITIZE_THREAD __attribute__((no_sanitize("thread")))
20-
extern "C" void __tsan_atomic32_load(uint32_t* addr, int order);
20+
extern "C" uint32_t __tsan_atomic32_load(uint32_t* addr, int order);
2121
extern "C" void __tsan_atomic32_store(uint32_t* addr,
2222
uint32_t value,
2323
int order);
24-
extern "C" void __tsan_atomic64_load(uint64_t* addr, int order);
24+
extern "C" uint64_t __tsan_atomic64_load(uint64_t* addr, int order);
2525
extern "C" void __tsan_atomic64_store(uint64_t* addr,
2626
uint64_t value,
2727
int order);
28+
extern "C" void __tsan_read1(void* addr);
29+
extern "C" void __tsan_read2(void* addr);
30+
extern "C" void __tsan_read4(void* addr);
31+
extern "C" void __tsan_read8(void* addr);
32+
extern "C" void __tsan_read16(void* addr);
33+
extern "C" void __tsan_write1(void* addr);
34+
extern "C" void __tsan_write2(void* addr);
35+
extern "C" void __tsan_write4(void* addr);
36+
extern "C" void __tsan_write8(void* addr);
37+
extern "C" void __tsan_write16(void* addr);
2838
#else
2939
#define NO_SANITIZE_THREAD
3040
#endif
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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+
// VMOptions=--experimental-shared-data
6+
7+
import "dart:io";
8+
import "dart:isolate";
9+
import "package:expect/expect.dart";
10+
11+
@pragma("vm:shared")
12+
List<dynamic> box = List<dynamic>.filled(1, 0);
13+
14+
@pragma("vm:never-inline")
15+
noopt() {}
16+
17+
@pragma("vm:never-inline")
18+
dataRaceFromMain() {
19+
final localBox = box;
20+
for (var i = 0; i < 1000000; i++) {
21+
localBox[0] += 1;
22+
noopt();
23+
}
24+
}
25+
26+
@pragma("vm:never-inline")
27+
dataRaceFromChild() {
28+
final localBox = box;
29+
for (var i = 0; i < 1000000; i++) {
30+
localBox[0] += 1;
31+
noopt();
32+
}
33+
}
34+
35+
child(_) {
36+
dataRaceFromChild();
37+
}
38+
39+
main(List<String> arguments) {
40+
if (arguments.contains("--testee")) {
41+
print(box); // side effect initialization
42+
Isolate.spawn(child, null);
43+
dataRaceFromMain();
44+
return;
45+
}
46+
47+
var exec = Platform.executable;
48+
var args = [
49+
...Platform.executableArguments,
50+
Platform.script.toFilePath(),
51+
"--testee",
52+
];
53+
print("+ $exec ${args.join(' ')}");
54+
55+
var result = Process.runSync(exec, args);
56+
print("Command stdout:");
57+
print(result.stdout);
58+
print("Command stderr:");
59+
print(result.stderr);
60+
61+
Expect.notEquals(0, result.exitCode);
62+
Expect.contains("ThreadSanitizer: data race", result.stderr);
63+
Expect.contains("of size 8", result.stderr);
64+
Expect.contains("dataRaceFromMain", result.stderr);
65+
Expect.contains("dataRaceFromChild", result.stderr);
66+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
// VMOptions=--experimental-shared-data
6+
7+
import "dart:io";
8+
import "dart:isolate";
9+
import "package:expect/expect.dart";
10+
11+
class Box {
12+
@pragma("vm:no-sanitize-thread") // __attribute__((no_sanitize("thread")))
13+
int foo = 0;
14+
}
15+
16+
@pragma("vm:shared")
17+
Box box = Box();
18+
19+
@pragma("vm:never-inline")
20+
noopt() {}
21+
22+
@pragma("vm:never-inline")
23+
dataRaceFromMain() {
24+
final localBox = box;
25+
for (var i = 0; i < 1000000; i++) {
26+
localBox.foo += 1;
27+
noopt();
28+
}
29+
}
30+
31+
@pragma("vm:never-inline")
32+
dataRaceFromChild() {
33+
final localBox = box;
34+
for (var i = 0; i < 1000000; i++) {
35+
localBox.foo += 1;
36+
noopt();
37+
}
38+
}
39+
40+
child(_) {
41+
dataRaceFromChild();
42+
}
43+
44+
main(List<String> arguments) {
45+
if (arguments.contains("--testee")) {
46+
print(box); // side effect initialization
47+
Isolate.spawn(child, null);
48+
dataRaceFromMain();
49+
return;
50+
}
51+
52+
var exec = Platform.executable;
53+
var args = [
54+
...Platform.executableArguments,
55+
Platform.script.toFilePath(),
56+
"--testee",
57+
];
58+
print("+ $exec ${args.join(' ')}");
59+
60+
var result = Process.runSync(exec, args);
61+
print("Command stdout:");
62+
print(result.stdout);
63+
print("Command stderr:");
64+
print(result.stderr);
65+
66+
Expect.equals(0, result.exitCode);
67+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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+
// VMOptions=--experimental-shared-data
6+
7+
import "dart:io";
8+
import "dart:isolate";
9+
import "package:expect/expect.dart";
10+
11+
class Box {
12+
int foo = 0;
13+
}
14+
15+
@pragma("vm:shared")
16+
Box box = Box();
17+
18+
@pragma("vm:never-inline")
19+
noopt() {}
20+
21+
@pragma("vm:never-inline")
22+
dataRaceFromMain() {
23+
final localBox = box;
24+
for (var i = 0; i < 1000000; i++) {
25+
localBox.foo += 1;
26+
noopt();
27+
}
28+
}
29+
30+
@pragma("vm:never-inline")
31+
dataRaceFromChild() {
32+
final localBox = box;
33+
for (var i = 0; i < 1000000; i++) {
34+
localBox.foo += 1;
35+
noopt();
36+
}
37+
}
38+
39+
child(_) {
40+
dataRaceFromChild();
41+
}
42+
43+
main(List<String> arguments) {
44+
if (arguments.contains("--testee")) {
45+
print(box); // side effect initialization
46+
Isolate.spawn(child, null);
47+
dataRaceFromMain();
48+
return;
49+
}
50+
51+
var exec = Platform.executable;
52+
var args = [
53+
...Platform.executableArguments,
54+
Platform.script.toFilePath(),
55+
"--testee",
56+
];
57+
print("+ $exec ${args.join(' ')}");
58+
59+
var result = Process.runSync(exec, args);
60+
print("Command stdout:");
61+
print(result.stdout);
62+
print("Command stderr:");
63+
print(result.stderr);
64+
65+
Expect.notEquals(0, result.exitCode);
66+
Expect.contains("ThreadSanitizer: data race", result.stderr);
67+
Expect.contains("of size 8", result.stderr);
68+
Expect.contains("dataRaceFromMain", result.stderr);
69+
Expect.contains("dataRaceFromChild", result.stderr);
70+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
// VMOptions=--experimental-shared-data
6+
7+
import "dart:io";
8+
import "dart:isolate";
9+
import "dart:typed_data";
10+
import "package:expect/expect.dart";
11+
12+
@pragma("vm:shared")
13+
Uint8List box = Uint8List(1);
14+
15+
@pragma("vm:never-inline")
16+
noopt() {}
17+
18+
@pragma("vm:never-inline")
19+
dataRaceFromMain() {
20+
final localBox = box;
21+
for (var i = 0; i < 1000000; i++) {
22+
localBox[0] += 1;
23+
noopt();
24+
}
25+
}
26+
27+
@pragma("vm:never-inline")
28+
dataRaceFromChild() {
29+
final localBox = box;
30+
for (var i = 0; i < 1000000; i++) {
31+
localBox[0] += 1;
32+
noopt();
33+
}
34+
}
35+
36+
child(_) {
37+
dataRaceFromChild();
38+
}
39+
40+
main(List<String> arguments) {
41+
if (arguments.contains("--testee")) {
42+
print(box); // side effect initialization
43+
Isolate.spawn(child, null);
44+
dataRaceFromMain();
45+
return;
46+
}
47+
48+
var exec = Platform.executable;
49+
var args = [
50+
...Platform.executableArguments,
51+
Platform.script.toFilePath(),
52+
"--testee",
53+
];
54+
print("+ $exec ${args.join(' ')}");
55+
56+
var result = Process.runSync(exec, args);
57+
print("Command stdout:");
58+
print(result.stdout);
59+
print("Command stderr:");
60+
print(result.stderr);
61+
62+
Expect.notEquals(0, result.exitCode);
63+
Expect.contains("ThreadSanitizer: data race", result.stderr);
64+
Expect.contains("of size 1", result.stderr);
65+
Expect.contains("dataRaceFromMain", result.stderr);
66+
Expect.contains("dataRaceFromChild", result.stderr);
67+
}

runtime/tests/vm/vm.status

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,9 @@ dart/finalizer/finalizer_isolate_groups_run_gc_test: SkipByDesign # uses spawnUr
392392
dart/isolates/send_object_to_spawn_uri_isolate_test: SkipByDesign # uses spawnUri
393393
dart/issue32950_test: SkipByDesign # uses spawnUri.
394394

395+
[ $runtime != dart_precompiled || $sanitizer != tsan ]
396+
dart/tsan/*: SkipByDesign
397+
395398
[ $runtime != dart_precompiled || $sanitizer != msan && $sanitizer != tsan ]
396399
dart/sanitizer_compatibility_test: SkipByDesign
397400

runtime/vm/app_snapshot.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2204,7 +2204,7 @@ class FieldSerializationCluster : public SerializationCluster {
22042204
s->Write<int8_t>(field->untag()->static_type_exactness_state_);
22052205
s->Write<uint32_t>(field->untag()->kernel_offset_);
22062206
}
2207-
s->Write<uint16_t>(field->untag()->kind_bits_);
2207+
s->Write<uint32_t>(field->untag()->kind_bits_);
22082208

22092209
// Write out either the initial static value or field offset.
22102210
if (Field::StaticBit::decode(field->untag()->kind_bits_)) {
@@ -2267,7 +2267,7 @@ class FieldDeserializationCluster : public DeserializationCluster {
22672267
#endif // defined(TARGET_ARCH_X64)
22682268
field->untag()->kernel_offset_ = d.Read<uint32_t>();
22692269
#endif
2270-
field->untag()->kind_bits_ = d.Read<uint16_t>();
2270+
field->untag()->kind_bits_ = d.Read<uint32_t>();
22712271

22722272
field->untag()->host_offset_or_field_id_ =
22732273
static_cast<SmiPtr>(d.ReadRef());

0 commit comments

Comments
 (0)