【文章翻譯】Announcing Dart 2.15

【文章內容使用 Gemini 1.5 Pro 自動翻譯產生】

原文:https://medium.com/dartlang/dart-2-15-7e7a598e508a

宣佈 Dart 2.15

今天,我們發佈了 Dart SDK 的 2.15 版本,它具有使用 worker isolates 的快速併發、一個新的構造函數 tear-off 語言功能、dart:core 函式庫中改進的列舉支援、套件發佈者的新功能等等。

Graphic listing new 2.15 features

使用 worker isolates 的快速併發

幾乎所有現代設備都具有多核心的 CPU,能夠並行運行多個任務。對於大多數 Dart 程式而言,這些核心的使用方式對開發人員來說是透明的:Dart 運行時系統預設在單核上運行所有 Dart 程式碼,但會使用其他核心來執行系統級任務,例如異步輸入/輸出,例如寫入檔案或進行網路呼叫。

但是您的 Dart 程式碼本身可能需要並行運行。例如,您可能有一個連續的動畫和一個長時間運行的任務,例如解析一個大型 JSON 檔案。如果額外任務花費的時間太長,則可能會導致 UI 中出現卡頓或延遲。透過將這些額外任務移動到單獨的核心,動畫可以繼續在主執行緒上運行,而不會中斷。

Dart 的併發模型基於 isolates——彼此隔離的獨立執行單元——以防止與共用記憶體相關的一大類併發程式設計錯誤,例如 資料競爭等競爭條件。Dart 透過不允許在 isolates 之間共用任何可變物件來防止這些錯誤,而是使用 isolates 使用 訊息傳遞 交換狀態的模型。在 Dart 2.15 中,我們對 isolates 進行了許多重大增強。

我們首先重新設計和重新實作了 isolates 的工作方式,引入了一個新概念:isolate 群組。isolate 群組中的 isolates 共用代表正在運行程式的各種內部資料結構。這使得群組中的個別 isolates 的成本要低得多。現在,在現有的 isolate 群組中啟動額外的 isolate 的速度要快 100 倍以上,因為我們不需要初始化程式結構,而且這些產生的 isolates 消耗的記憶體減少了 10-100 倍。

雖然 isolate 群組仍然可以防止在 isolates 之間共用可變物件,但該群組是使用共用堆實作的,這解鎖了更多功能。我們可以將物件從一個 isolate 傳遞到另一個 isolate,這可以用於執行返回大量記憶體的任務的 worker isolates。例如,一個 worker isolate 進行網路呼叫以獲取資料,將該資料解析為一個大型 JSON 物件圖,然後將該 JSON 圖返回給主 isolate。在 Dart 2.15 之前,該結果需要進行深度複製,如果複製花費的時間超過畫面預算,則本身可能會導致 UI 卡頓。

在 2.15 中,worker isolate 可以呼叫 Isolate.exit(),將其結果作為參數傳遞。然後,Dart 運行時將包含結果的記憶體從 worker isolate 傳遞到主 isolate,而無需複製,並且主 isolate 可以在恆定時間內接收結果。我們在 Flutter 2.8 中更新了 compute() 工具函數,以利用 Isolate.exit()。如果您已經在使用 compute(),那麼在升級到 Flutter 2.8 後,您將自動獲得這些效能提升。

最後,我們重新設計了 isolate 訊息傳遞機制,使傳遞中小型的訊息的速度提高了大約 8 倍。傳送速度明顯加快,接收訊息幾乎總是在恆定時間內完成。我們還擴展了 isolates 可以相互傳送的物件類型,加入了對函數類型、閉包和堆疊追蹤物件的支援。有關詳細資訊,請參閱 SendPort.send() 的 API 文件。

要了解有關如何使用 isolates 的更多資訊,請參閱我們為 2.15 加入的新的 Dart 中的併發 文件。我們還有一些 程式碼範例 供您查看。

新的語言功能:構造函數 tear-offs

在 Dart 中,您可以透過使用函數的名稱來建立一個指向另一個物件上的函數的函數物件。在以下範例中,main() 方法的第二行說明了當它將 g 設定為 m.greet 時的語法:

1
2
3
4
5
6
7
8
class Greeter {
final String name;
Greeter(this.name);

void greet(String who) {
print('$name says: Hello $who!');
}
}
1
2
3
4
5
void main() {
final m = Greeter('Michael');
final g = m.greet; // g 保存指向 m.greet 的函數指標。
g('Leaf'); // 呼叫並列印 "Michael says: Hello Leaf!"
}

這種函數指標——也稱為函數 tear-offs ——在使用 Dart 核心函式庫時經常出現。以下是一個透過傳遞函數指標來呼叫可迭代物件上的 foreach() 的範例:

1
2
3
4
5
6
final m = Greeter('Michael');

['Lasse', 'Bob', 'Erik'].forEach(m.greet);

// 列印 "Michael says: Hello Lasse!", "Michael says: Hello Bob!",
// "Michael says: Hello Erik!"

從歷史上看,我們不支援從構造函數建立 tear-offs(語言問題 #216)。這很煩人,因為在許多情況下——例如,在構建 Flutter UI 時——構造函數 tear-off 正是您需要的。從 Dart 2.15 開始,現在支援此語法。以下是一個透過呼叫 .map() 並將其傳遞給 Text 的構造函數的 tear-off 來構建包含三個 Text widget 的 Column widget 的範例。

1
2
3
4
5
6
7
class FruitWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: ['Apple', 'Orange'].map(Text.new).toList());
}
}

Text.new 指的是 Text 類別的預設構造函數。您也可以參考命名構造函數——例如,.map(Text.rich)

相關的語言變更

在實作構造函數 tear-offs 的同時,我們藉此機會修復了對函數指標的現有支援中的一些不一致之處。您現在可以特化泛型方法來建立非泛型方法:

1
2
3
T id<T>(T value) => value;
var intId = id<int>; // 2.15 中的新功能。
int Function(int) intId = id; // 2.15 之前的解決方法。

您甚至可以特化泛型函數物件來建立非泛型函數物件:

1
2
const fo = id; // 撕掉 `id`,建立一個函數物件。
const c1 = fo<int>; // 2.15 中的新功能;之前是錯誤的。

最後,我們清理了涉及泛型的類型文字:

1
2
3
var y = List; // 已經支援。
var z = List<int>; // 2.15 中的新功能。
var z = typeOf<List<int>>(); // 2.15 之前的解決方法。

改進 dart:core 函式庫中的列舉

我們對 dart:core 函式庫中的列舉 API 進行了一些便利的補充(語言問題 #1511)。您現在可以使用 .name 獲取每個列舉值的字串值:

1
2
3
4
5
6
7
enum MyEnum {
one, two, three
}

void main() {
print(MyEnum.one.name); // 列印 "one"。
}

您也可以按名稱查詢列舉值:

1
print(MyEnum.values.byName('two') == MyEnum.two);  // 列印 "true"。

最後,您可以獲取所有名稱值對的映射:

1
2
final map = MyEnum.values.asNameMap();
print(map['three'] == MyEnum.three); // 列印 "true"。

有關使用這些新 API 的範例,請參閱 此 Flutter PR

壓縮指標

Dart 2.15 加入了對壓縮指標的支援,這是一種技術,如果只需要支援 32 位地址空間(最多 4 GB 記憶體),則 64 位 SDK 可以使用更節省空間的指標表示形式。壓縮指標會顯著減少記憶體;在我們使用 GPay 應用程式進行的內部測試中,我們發現 Dart 堆大小減少了大約 10%。

由於壓縮指標意味著無法定址 4 GB 以上的任何可用 RAM,因此該功能位於 Dart SDK 中的一個設定選項之後,只有在構建 SDK 時,Dart SDK 的嵌入器才能切換該選項。Flutter SDK 版本 2.8 已為 Android 構建啟用此設定,Flutter 團隊正在考慮在未來的版本中 也為 iOS 構建啟用它

Dart SDK 中包含的 Dart DevTools

DevTools 套件 的除錯和效能工具以前不在 Dart SDK 中;您必須單獨下載它。從 Dart 2.15 開始,您現在下載 Dart SDK 時就可以獲得 DevTools,而無需進一步的安裝步驟。有關將 DevTools 與 Dart 命令列應用程式一起使用的更多資訊,請參閱 DevTools 文件

套件發佈者的新 pub 功能

Dart 2.15 SDK 在 dart pub 開發人員命令和 pub.dev 套件儲存庫中還有兩個新功能。

首先,有一個針對套件發佈者的新安全功能。目的是檢測發佈者何時意外地將機密——例如雲端或 CI 憑證——發佈到 pub 套件中。在了解到在 GitHub 儲存庫中,每天都會 洩露數千個機密 後,我們受到啟發加入了此洩露檢測功能。

洩露檢測作為 dart pub publish 命令中運行的預發佈驗證的一部分運行。如果它在即將發佈的檔案中檢測到潛在的機密,則發佈命令將在不發佈的情況下退出,並列印如下輸出:

1
2
3
4
5
6
7
8
9
10
11
12
Publishing my_package 1.0.0 to https://pub.dartlang.org:
Package validation found the following errors:
* line 1, column 1 of lib/key.pem: Potential leak of Private Key detected.

1 │ ┌ - - -BEGIN PRIVATE KEY - - -
2 │ │ H0M6xpM2q+53wmsN/eYLdgtjgBd3DBmHtPilCkiFICXyaA8z9LkJ
3 │ └ - - -END PRIVATE KEY - - -

* line 2, column 23 of lib/my_package.dart: Potential leak of Google OAuth Refresh Token detected.

2 │ final refreshToken = "1//042ys8uoFwZrkCgYIARAAGAQSNwF-L9IrXmFYE-sfKefSpoCnyqEcsHX97Y90KY-p8TPYPPnY2IPgRXdy0QeVw7URuF5u9oUeIF0";

在極少數情況下,此檢測可能會出現誤報,將您實際上打算發佈的內容或檔案標記為潛在洩露。在這些情況下,您可以將檔案加入 允許清單

其次,我們為發佈者加入了另一個功能,支援撤回已發佈的套件版本。當發佈了錯誤的套件版本時,我們通常建議發佈一個新的次要版本,以修復意外問題。在極少數情況下——例如,當您還沒有這樣的修復程式時,或者當您意外發佈了一個新的主要版本但打算發佈一個新的次要版本時——您可以使用新的 套件撤回功能 作為最後的手段。此功能在 pub.dev 上的管理 UI 中提供:

Screenshot of the package retraction UI

當套件版本被撤回時,pub 用戶端在 pub get 或 pub upgrade 中不再解析到該版本。如果任何開發人員已經解析到撤回的版本(因此它在他們的 pubspec.lock 檔案中),他們下次運行 pub 時會看到一條警告:

1
2
3
4
$ dart pub get
Resolving dependencies…
mypkg 0.0.181-buggy (retracted, 0.0.182-fixed available)
Got dependencies!

用於檢測雙向 Unicode 字元的安全分析 (CVE-2021–22567)

最近發現了一個涉及雙向 Unicode 字元的一般程式設計語言漏洞 (CVE-2021–42574)。此漏洞影響支援 Unicode 的大多數現代程式設計語言。以下 Dart 原始程式碼說明了該問題:

1
2
3
4
5
6
7
8
9
main() {
final accessLevel = 'user';

if (accessLevel == 'user‮ .⁦// Check if admin⁩ ⁦') {
print('You are a regular user.');
} else {
print('You are an admin.');
}
}

您可能會認為這個程式會列印 *You are a regular user.*,但實際上它可能會列印 *You are an admin.*!透過使用包含雙向 Unicode 字元的字串,可以利用此漏洞。這些字元會將文字的方向從左到右更改為從右到左,然後再返回,所有這些都在同一行中。使用雙向字元時,文字在螢幕上的渲染方式可能與實際文字內容大不相同。您可以在 此 GitHub 程式碼 gist 中看到一個範例。

針對此漏洞的緩解措施包括使用檢測雙向 Unicode 字元的工具(編輯器、程式碼審查工具等),以便開發人員可以意識到它們,並有意識地接受它們的使用。上面連結到的 GitHub gist 檔案檢視器就是一個顯示這些字元的工具的範例。

Dart 2.15 引入了一個進一步的緩解措施(Dart 安全公告 CVE-2021–22567):Dart 分析器現在會掃描雙向 Unicode 字元,並標記任何使用它們的情況:

1
2
3
4
5
6
7
8
$ dart analyze
Analyzing cvetest... 2.6s

info • bin/cvetest.dart:4:27 • The Unicode code point 'U+202E'
changes the appearance of text from how it's interpreted
by the compiler. Try removing the code point or using the
Unicode escape sequence '\u202E'. •
text_direction_code_point_in_literal

我們建議將這些字元替換為 Unicode 跳脫序列,以便它們在任何文字編輯器或檢視器中都可見。或者,如果您確實有合法使用這些字元的情況,您可以透過在使用前的行中加入覆蓋來禁用警告:

1
// ignore: text_direction_code_point_in_literal

使用第三方 pub 伺服器時的 Pub.dev 憑證漏洞 (CVE-2021–22568)

我們還發佈了第二個與 pub.dev 相關的 Dart 安全公告:CVE-2021–22568。此公告針對可能已將套件發佈到第三方 pub 套件伺服器(例如私人或公司內部套件伺服器)的套件發佈者。發佈到公共 pub.dev 儲存庫(標準設定)的開發人員不受此漏洞的影響。

如果您已發佈到第三方儲存庫,則該漏洞是:提交給該第三方儲存庫進行身份驗證的 OAuth2 臨時(一小時)存取權杖可能會被濫用於對公共 pub.dev 儲存庫進行身份驗證。因此,惡意的第三方 pub 伺服器可能會使用存取權杖在 pub.dev 上冒充您並在那裡發佈套件。如果您已將套件發佈到不受信任的第三方套件儲存庫,請考慮審計您在 pub.dev 公共套件儲存庫上的所有帳戶活動。您可以使用 pub.dev 活動日誌 來完成此目的。

結語

我們希望您會喜歡 Dart 2.15 中的這些新功能,今天即可使用。這是我們今年的最後一個版本,我們想藉此機會感謝 Dart 生態系統。感謝所有出色的回饋,感謝您在我們不斷發展中提供的持續支援,感謝您在過去一年中透過在 pub.dev 上發佈數千個套件來擴展我們的生態系統。我們迫不及待地想在明年重新開始,我們為 2022 年計劃了很多令人興奮的事情。在此之前,祝您假期愉快!