Skip to content

Commit 3594875

Browse files
authored
[CQ] ensure null-safe read actions (#8060)
Add a null-safe `safeRunReadAction` and migrate unsafe callers to use it. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md
1 parent 2e8af12 commit 3594875

File tree

11 files changed

+64
-27
lines changed

11 files changed

+64
-27
lines changed

flutter-idea/src/io/flutter/bazel/PluginConfig.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010
import com.google.gson.Gson;
1111
import com.google.gson.JsonSyntaxException;
1212
import com.google.gson.annotations.SerializedName;
13-
import com.intellij.openapi.application.ApplicationManager;
1413
import com.intellij.openapi.diagnostic.Logger;
1514
import com.intellij.openapi.util.Computable;
1615
import com.intellij.openapi.vfs.VirtualFile;
1716
import io.flutter.FlutterUtils;
17+
import io.flutter.utils.OpenApiUtils;
1818
import org.jetbrains.annotations.NotNull;
1919
import org.jetbrains.annotations.Nullable;
2020

@@ -43,6 +43,7 @@ String getDaemonScript() {
4343
String getDevToolsScript() {
4444
return fields.devToolsScript;
4545
}
46+
4647
@Nullable
4748
String getDoctorScript() {
4849
return fields.doctorScript;
@@ -137,7 +138,7 @@ public static PluginConfig load(@NotNull VirtualFile file) {
137138
}
138139
};
139140

140-
return ApplicationManager.getApplication().runReadAction(readAction);
141+
return OpenApiUtils.safeRunReadAction(readAction);
141142
}
142143

143144
@VisibleForTesting

flutter-idea/src/io/flutter/bazel/Workspace.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import com.google.common.base.Joiner;
1010
import com.google.common.base.Objects;
1111
import com.google.common.collect.ImmutableSet;
12-
import com.intellij.openapi.application.ApplicationManager;
1312
import com.intellij.openapi.diagnostic.Logger;
1413
import com.intellij.openapi.module.Module;
1514
import com.intellij.openapi.project.Project;
@@ -18,6 +17,7 @@
1817
import com.intellij.openapi.util.Computable;
1918
import com.intellij.openapi.vfs.InvalidVirtualFileAccessException;
2019
import com.intellij.openapi.vfs.VirtualFile;
20+
import io.flutter.utils.OpenApiUtils;
2121
import org.jetbrains.annotations.NotNull;
2222
import org.jetbrains.annotations.Nullable;
2323

@@ -379,7 +379,7 @@ private static VirtualFile findWorkspaceFile(@NotNull Project p) {
379379
// not found
380380
return null;
381381
};
382-
return ApplicationManager.getApplication().runReadAction(readAction);
382+
return OpenApiUtils.safeRunReadAction(readAction);
383383
}
384384

385385
/**

flutter-idea/src/io/flutter/pub/PubRoot.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,17 @@
88
import com.intellij.openapi.actionSystem.AnActionEvent;
99
import com.intellij.openapi.actionSystem.CommonDataKeys;
1010
import com.intellij.openapi.actionSystem.LangDataKeys;
11-
import com.intellij.openapi.application.ApplicationManager;
1211
import com.intellij.openapi.module.Module;
1312
import com.intellij.openapi.module.ModuleManager;
1413
import com.intellij.openapi.project.Project;
1514
import com.intellij.openapi.roots.ModuleRootManager;
1615
import com.intellij.openapi.roots.ProjectFileIndex;
1716
import com.intellij.openapi.roots.ProjectRootManager;
18-
import com.intellij.openapi.util.Computable;
1917
import com.intellij.openapi.vfs.VirtualFile;
2018
import com.intellij.psi.PsiFile;
2119
import com.jetbrains.lang.dart.util.DotPackagesFileUtil;
2220
import io.flutter.FlutterUtils;
21+
import io.flutter.utils.OpenApiUtils;
2322
import org.jetbrains.annotations.NotNull;
2423
import org.jetbrains.annotations.Nullable;
2524

@@ -74,6 +73,7 @@ public static PubRoot forFile(@Nullable VirtualFile file) {
7473
*/
7574
@Nullable
7675
public static PubRoot forEventWithRefresh(@NotNull final AnActionEvent event) {
76+
assert CommonDataKeys.PSI_FILE != null;
7777
final PsiFile psiFile = CommonDataKeys.PSI_FILE.getData(event.getDataContext());
7878
if (psiFile != null) {
7979
final PubRoot root = forPsiFile(psiFile);
@@ -82,6 +82,7 @@ public static PubRoot forEventWithRefresh(@NotNull final AnActionEvent event) {
8282
}
8383
}
8484

85+
assert LangDataKeys.MODULE != null;
8586
final Module module = LangDataKeys.MODULE.getData(event.getDataContext());
8687
if (module != null) {
8788
final List<PubRoot> roots = PubRoots.forModule(module);
@@ -121,8 +122,10 @@ public static PubRoot forPsiFile(@NotNull PsiFile psiFile) {
121122
*/
122123
@Nullable
123124
public static PubRoot forDescendant(@NotNull VirtualFile fileOrDir, @NotNull Project project) {
124-
final ProjectFileIndex index = ProjectRootManager.getInstance(project).getFileIndex();
125-
return ApplicationManager.getApplication().runReadAction((Computable<PubRoot>)() -> {
125+
ProjectRootManager manager = ProjectRootManager.getInstance(project);
126+
if (manager == null) return null;
127+
final ProjectFileIndex index = manager.getFileIndex();
128+
return OpenApiUtils.safeRunReadAction(() -> {
126129
final VirtualFile root = index.getContentRootForFile(fileOrDir);
127130
return forDirectory(root);
128131
});

flutter-idea/src/io/flutter/run/FlutterPositionMapper.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@
66
package io.flutter.run;
77

88
import com.google.common.annotations.VisibleForTesting;
9-
import com.intellij.openapi.application.ApplicationManager;
109
import com.intellij.openapi.diagnostic.Logger;
1110
import com.intellij.openapi.project.Project;
12-
import com.intellij.openapi.util.Computable;
1311
import com.intellij.openapi.util.text.StringUtil;
1412
import com.intellij.openapi.vfs.LocalFileSystem;
1513
import com.intellij.openapi.vfs.VirtualFile;
@@ -24,6 +22,7 @@
2422
import com.jetbrains.lang.dart.util.DartUrlResolver;
2523
import io.flutter.FlutterUtils;
2624
import io.flutter.dart.DartPlugin;
25+
import io.flutter.utils.OpenApiUtils;
2726
import io.flutter.vmService.DartVmServiceDebugProcess;
2827
import org.dartlang.vm.service.element.LibraryRef;
2928
import org.dartlang.vm.service.element.Script;
@@ -150,7 +149,7 @@ private String findRemoteSourceRoot(String remotePath) {
150149
if (project == null || project.isDisposed()) return null;
151150

152151
// Find files with the same filename (matching the suffix after the last slash).
153-
final PsiFile[] localFilesWithSameName = ApplicationManager.getApplication().runReadAction((Computable<PsiFile[]>)() -> {
152+
final PsiFile[] localFilesWithSameName = OpenApiUtils.safeRunReadAction(() -> {
154153
final String remoteFileName = PathUtil.getFileName(remotePath);
155154
final GlobalSearchScope scope = GlobalSearchScopesCore.directoryScope(project, sourceRoot, true);
156155
return FilenameIndex.getFilesByName(project, remoteFileName, scope);
@@ -304,7 +303,7 @@ protected VirtualFile findLocalFile(@NotNull String uri) {
304303
*/
305304
@Nullable
306305
protected VirtualFile findLocalFile(@NotNull String uri, CompletableFuture<String> fileFuture) {
307-
return ApplicationManager.getApplication().runReadAction((Computable<VirtualFile>)() -> {
306+
return OpenApiUtils.safeRunReadAction(() -> {
308307
// This can be a remote file or URI.
309308
if (remoteSourceRoot != null && uri.startsWith(remoteSourceRoot)) {
310309
final String rootUri = StringUtil.trimEnd(resolver.getDartUrlForFile(sourceRoot), '/');

flutter-idea/src/io/flutter/run/FlutterReloadManager.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,14 @@
1414
import com.intellij.execution.ExecutionException;
1515
import com.intellij.execution.configurations.GeneralCommandLine;
1616
import com.intellij.ide.actions.SaveAllAction;
17-
import com.intellij.notification.*;
18-
import com.intellij.openapi.actionSystem.*;
17+
import com.intellij.notification.Notification;
18+
import com.intellij.notification.NotificationGroup;
19+
import com.intellij.notification.NotificationGroupManager;
20+
import com.intellij.notification.NotificationType;
21+
import com.intellij.openapi.actionSystem.AnAction;
22+
import com.intellij.openapi.actionSystem.AnActionEvent;
23+
import com.intellij.openapi.actionSystem.CommonDataKeys;
24+
import com.intellij.openapi.actionSystem.DataContext;
1925
import com.intellij.openapi.actionSystem.ex.AnActionListener;
2026
import com.intellij.openapi.application.ApplicationManager;
2127
import com.intellij.openapi.application.ModalityState;
@@ -30,7 +36,6 @@
3036
import com.intellij.openapi.fileEditor.FileEditorManager;
3137
import com.intellij.openapi.project.Project;
3238
import com.intellij.openapi.ui.popup.Balloon;
33-
import com.intellij.openapi.util.Computable;
3439
import com.intellij.openapi.util.SystemInfo;
3540
import com.intellij.openapi.util.io.FileUtil;
3641
import com.intellij.openapi.vfs.VirtualFile;
@@ -59,6 +64,7 @@
5964
import io.flutter.settings.FlutterSettings;
6065
import io.flutter.utils.FlutterModuleUtils;
6166
import io.flutter.utils.MostlySilentColoredProcessHandler;
67+
import io.flutter.utils.OpenApiUtils;
6268
import org.jetbrains.annotations.NotNull;
6369
import org.jetbrains.annotations.Nullable;
6470

@@ -194,7 +200,7 @@ public void beforeAllDocumentsSaving() {
194200

195201
ApplicationManager.getApplication().invokeLater(() -> {
196202
// Find a Dart editor to trigger the reload.
197-
final Editor anEditor = ApplicationManager.getApplication().runReadAction((Computable<Editor>)() -> {
203+
final Editor anEditor = OpenApiUtils.safeRunReadAction(() -> {
198204
Editor someEditor = null;
199205
final EditorFactory editorFactory = EditorFactory.getInstance();
200206
if(editorFactory != null) {
@@ -479,7 +485,7 @@ private boolean hasErrorsInFile(@NotNull Document document) {
479485
// are analysis issues in other files; the compilation errors from the flutter tool
480486
// will indicate to the user where the problems are.
481487

482-
final PsiErrorElement firstError = ApplicationManager.getApplication().runReadAction((Computable<PsiErrorElement>)() -> {
488+
final PsiErrorElement firstError = OpenApiUtils.safeRunReadAction(() -> {
483489
final PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(document);
484490
if (psiFile instanceof DartFile) {
485491
return PsiTreeUtil.findChildOfType(psiFile, PsiErrorElement.class, false);

flutter-idea/src/io/flutter/run/bazelTest/BazelTestRunner.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,15 @@
1919
import com.intellij.execution.runners.ExecutionEnvironment;
2020
import com.intellij.execution.runners.GenericProgramRunner;
2121
import com.intellij.execution.ui.RunContentDescriptor;
22-
import com.intellij.openapi.application.ApplicationManager;
2322
import com.intellij.openapi.diagnostic.Logger;
2423
import com.intellij.openapi.module.Module;
2524
import com.intellij.openapi.project.Project;
2625
import com.intellij.openapi.roots.ContentEntry;
2726
import com.intellij.openapi.roots.ModuleRootManager;
28-
import com.intellij.openapi.util.Computable;
2927
import com.intellij.openapi.util.Key;
28+
import com.intellij.openapi.util.text.StringUtil;
3029
import com.intellij.openapi.vfs.LocalFileSystem;
3130
import com.intellij.openapi.vfs.VirtualFile;
32-
import com.intellij.openapi.util.text.StringUtil;
3331
import com.intellij.xdebugger.XDebugProcess;
3432
import com.intellij.xdebugger.XDebugProcessStarter;
3533
import com.intellij.xdebugger.XDebugSession;
@@ -46,6 +44,7 @@
4644
import io.flutter.run.test.FlutterTestRunner;
4745
import io.flutter.settings.FlutterSettings;
4846
import io.flutter.utils.JsonUtils;
47+
import io.flutter.utils.OpenApiUtils;
4948
import io.flutter.utils.StdoutJsonParser;
5049
import io.flutter.utils.UrlUtils;
5150
import org.jetbrains.annotations.NotNull;
@@ -284,7 +283,7 @@ protected VirtualFile findLocalFile(@NotNull final String uri) {
284283
final String pathFromWorkspace = uri.substring(workspaceDirName.length() + 1);
285284

286285
// For each root in each module, look for a bazel workspace path, if found attempt to compute the VirtualFile, return when found.
287-
return ApplicationManager.getApplication().runReadAction((Computable<VirtualFile>)() -> {
286+
return OpenApiUtils.safeRunReadAction(() -> {
288287
for (Module module : DartSdkLibUtil.getModulesWithDartSdkEnabled(getProject())) {
289288
for (ContentEntry contentEntry : ModuleRootManager.getInstance(module).getContentEntries()) {
290289
final VirtualFile includedRoot = contentEntry.getFile();

flutter-idea/src/io/flutter/utils/EnableDartSupportForModule.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public void run() {
4242
}
4343
});
4444
ApplicationManager.getApplication().executeOnPooledThread(() -> {
45-
ApplicationManager.getApplication().runReadAction(() -> {
45+
OpenApiUtils.safeRunReadAction(() -> {
4646
DartAnalysisServerService.getInstance(project).serverReadyForRequest();
4747
});
4848
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2025 The Chromium Authors. All rights reserved.
3+
* Use of this source code is governed by a BSD-style license that can be
4+
* found in the LICENSE file.
5+
*/
6+
package io.flutter.utils;
7+
8+
import com.intellij.openapi.application.Application;
9+
import com.intellij.openapi.application.ApplicationManager;
10+
import com.intellij.openapi.util.Computable;
11+
import org.jetbrains.annotations.NotNull;
12+
import org.jetbrains.annotations.Nullable;
13+
14+
public class OpenApiUtils {
15+
16+
public static void safeRunReadAction(@NotNull Runnable runnable) {
17+
Application application = ApplicationManager.getApplication();
18+
if (application == null) return;
19+
application.runReadAction(runnable);
20+
}
21+
22+
public static <T> @Nullable T safeRunReadAction(@NotNull Computable<T> computable) {
23+
Application application = ApplicationManager.getApplication();
24+
if (application == null) return null;
25+
return application.runReadAction(computable);
26+
}
27+
}

flutter-idea/src/io/flutter/vmService/VmServiceWrapper.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,15 @@
2727
import io.flutter.run.daemon.FlutterApp;
2828
import io.flutter.sdk.FlutterSdk;
2929
import io.flutter.sdk.FlutterSdkVersion;
30+
import io.flutter.utils.OpenApiUtils;
3031
import io.flutter.vmService.frame.DartAsyncMarkerFrame;
3132
import io.flutter.vmService.frame.DartVmServiceEvaluator;
3233
import io.flutter.vmService.frame.DartVmServiceStackFrame;
3334
import io.flutter.vmService.frame.DartVmServiceValue;
3435
import org.dartlang.vm.service.VmService;
3536
import org.dartlang.vm.service.consumer.*;
36-
import org.dartlang.vm.service.element.Stack;
3737
import org.dartlang.vm.service.element.*;
38+
import org.dartlang.vm.service.element.Stack;
3839
import org.jetbrains.annotations.NotNull;
3940
import org.jetbrains.annotations.Nullable;
4041

@@ -295,7 +296,7 @@ public void attachIsolate(@NotNull IsolateRef isolateRef, @NotNull Isolate isola
295296
// Just to make sure that the main isolate is not handled twice, both from handleDebuggerConnected() and DartVmServiceListener.received(PauseStart)
296297
if (newIsolate) {
297298
XDebugSessionImpl session = (XDebugSessionImpl)myDebugProcess.getSession();
298-
ApplicationManager.getApplication().runReadAction(() -> {
299+
OpenApiUtils.safeRunReadAction(() -> {
299300
session.reset();
300301
session.initBreakpoints();
301302
});

flutter-idea/src/io/flutter/vmService/frame/DartVmServiceValue.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.intellij.xdebugger.frame.presentation.XKeywordValuePresentation;
1212
import com.intellij.xdebugger.frame.presentation.XNumericValuePresentation;
1313
import com.intellij.xdebugger.frame.presentation.XStringValuePresentation;
14+
import io.flutter.utils.OpenApiUtils;
1415
import io.flutter.utils.TypedDataList;
1516
import io.flutter.vmService.DartVmServiceDebugProcess;
1617
import io.flutter.vmService.VmServiceConsumers;
@@ -145,7 +146,7 @@ private static void reportSourcePosition(@NotNull final DartVmServiceDebugProces
145146

146147
ApplicationManager.getApplication().executeOnPooledThread(() -> {
147148
final XSourcePosition sourcePosition = debugProcess.getSourcePosition(isolateId, script, tokenPos);
148-
ApplicationManager.getApplication().runReadAction(() -> navigatable.setSourcePosition(sourcePosition));
149+
OpenApiUtils.safeRunReadAction(() -> navigatable.setSourcePosition(sourcePosition));
149150
});
150151
}
151152

0 commit comments

Comments
 (0)