mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-10-01 06:51:17 +07:00
Merge remote-tracking branch 'origin/master' into numpy-array-view
This commit is contained in:
Generated
+3
-3
@@ -290,9 +290,9 @@
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="jet">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2"/>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4"/>
|
||||
<option name="TAB_SIZE" value="2"/>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="ruby">
|
||||
|
||||
Generated
+1
@@ -27,6 +27,7 @@
|
||||
</annotationProcessing>
|
||||
<bytecodeTargetLevel target="1.6">
|
||||
<module name="annotations" target="1.5" />
|
||||
<module name="gradle-jps-plugin" target="1.5" />
|
||||
<module name="groovy-rt-constants" target="1.5" />
|
||||
<module name="groovy_rt" target="1.5" />
|
||||
<module name="intellilang-jps-plugin" target="1.6" />
|
||||
|
||||
Generated
+3
-3
@@ -1,12 +1,12 @@
|
||||
<component name="libraryTable">
|
||||
<library name="JSch">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/lib/jsch-0.1.50.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/lib/jsch-0.1.51.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$PROJECT_DIR$/lib/src/jsch-0.1.50.zip!/jsch-0.1.50/src/main/java" />
|
||||
<root url="jar://$PROJECT_DIR$/lib/src/jsch-0.1.50.zip!/jsch-0.1.50/examples" />
|
||||
<root url="jar://$PROJECT_DIR$/lib/src/jsch-0.1.51.zip!/jsch-0.1.51/src/main/java" />
|
||||
<root url="jar://$PROJECT_DIR$/lib/src/jsch-0.1.51.zip!/jsch-0.1.51/examples" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
Generated
+2
-2
@@ -1,11 +1,11 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Netty">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/lib/netty-all-4.1.0.Beta1.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/lib/netty-all-4.1.0.Beta3.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$PROJECT_DIR$/lib/src/netty-all-4.1.0.Beta1-sources.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/lib/src/netty-all-4.1.0.Beta3-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -36,4 +36,6 @@
|
||||
1.5 => plugins/Groovy/lib/groovy_rt.jar
|
||||
1.5 => lib/annotations.jar
|
||||
|
||||
1.5 => plugins/gradle/lib/gradle-jps-plugin.jar
|
||||
|
||||
1.3 => plugins/junit/lib/junit-rt.jar
|
||||
@@ -554,14 +554,7 @@ public def layoutCommunityPlugins(String home) {
|
||||
}
|
||||
}
|
||||
|
||||
pluginDir("java-decompiler") {
|
||||
dir("lib") {
|
||||
jar("java-decompiler.jar") {
|
||||
module("java-decompiler")
|
||||
}
|
||||
fileset(file: "$home/plugins/java-decompiler/lib/fernflower.jar")
|
||||
}
|
||||
}
|
||||
layoutPlugin("java-decompiler")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ libraryLicense(name: "JGoodies Forms", libraryName: "jgoodies-forms", version: "
|
||||
libraryLicense(name: "JGoodies Looks", libraryName: "jgoodies-looks", version: "2.4.2", license: "BSD ", url: "http://www.jgoodies.com/freeware/looks/", licenseUrl: "http://www.jgoodies.com/downloads/libraries.html")
|
||||
libraryLicense(name: "JGoodies Common", libraryName: "jgoodies-common", version: "1.2.1", license: "BSD ", url: "http://www.jgoodies.com/freeware/looks/", licenseUrl: "http://www.jgoodies.com/downloads/libraries.html")
|
||||
libraryLicense(name: "JNA", libraryName: "jna", version: "3.4.0", license: "LGPL 2.1", url: "https://jna.dev.java.net/", licenseUrl: "http://www.opensource.org/licenses/lgpl-2.1.php")
|
||||
libraryLicense(name: "JSch", libraryName: "JSch", version: "0.1.50", license: "BSD", url: "http://www.jcraft.com/jsch/", licenseUrl: "http://www.jcraft.com/jsch/LICENSE.txt")
|
||||
libraryLicense(name: "JSch", libraryName: "JSch", version: "0.1.51", license: "BSD", url: "http://www.jcraft.com/jsch/", licenseUrl: "http://www.jcraft.com/jsch/LICENSE.txt")
|
||||
libraryLicense(name: "jsch-agent-proxy", libraryName: "jsch-agent-proxy", version: "0.0.6", license: "BSD", url: "https://github.com/ymnk/jsch-agent-proxy", licenseUrl: "https://github.com/ymnk/jsch-agent-proxy/blob/master/LICENSE.txt")
|
||||
libraryLicense(name: "jsch-agent-proxy for svnkit trilead", libraryName: "jsch.agentproxy.svnkit-trilead-ssh2.jar", version: "0.0.6", license: "BSD", url: "https://github.com/ymnk/jsch-agent-proxy", licenseUrl: "https://github.com/ymnk/jsch-agent-proxy/blob/master/LICENSE.txt")
|
||||
libraryLicense(name: "JUnit", libraryName: "JUnit3", version: "3.8.1", license: "CPL 1.0", url: "http://junit.org/")
|
||||
@@ -252,7 +252,7 @@ libraryLicense(name: "XML-RPC", libraryName: "XmlRPC", version: "2.0", license:
|
||||
libraryLicense(name: "XStream", version: "1.4.3", license: "BSD", url: "http://xstream.codehaus.org/", licenseUrl: "http://xstream.codehaus.org/license.html")
|
||||
libraryLicense(name: "YourKit Java Profiler", libraryName: "yjp-controller-api-redist.jar", version: "8.0.x", license: "link (commercial license)", url: "http://yourkit.com/", licenseUrl: "http://www.yourkit.com/purchase/license.html")
|
||||
libraryLicense(name: "protobuf", version: "2.5.0", license: "New BSD", url: "http://code.google.com/p/protobuf/", licenseUrl: "http://code.google.com/p/protobuf/source/browse/trunk/COPYING.txt?r=367")
|
||||
libraryLicense(name: "Netty", libraryName: "Netty", version: "4.1.0.Beta1", license: "Apache 2.0", url: "http://netty.io", licenseUrl: "http://www.apache.org/licenses/LICENSE-2.0")
|
||||
libraryLicense(name: "Netty", libraryName: "Netty", version: "4.1.0.Beta3", license: "Apache 2.0", url: "http://netty.io", licenseUrl: "http://www.apache.org/licenses/LICENSE-2.0")
|
||||
libraryLicense(name: "Kryo", libraryName: "Kryo", version: "2.22", license: "New BSD License", url: "https://github.com/EsotericSoftware/kryo", licenseUrl: "https://github.com/EsotericSoftware/kryo/blob/master/license.txt")
|
||||
libraryLicense(name: "Snappy-Java", libraryName: "Snappy-Java", version: "0.3", license: "Apache 2.0", url: "https://github.com/dain/snappy", licenseUrl: "http://www.apache.org/licenses/LICENSE-2.0")
|
||||
libraryLicense(name: "Cucumber-Java", libraryName: "cucumber-java", version: "1.0.14", license: "MIT License", url: "https://github.com/cucumber/cucumber-jvm/", licenseUrl: "http://www.opensource.org/licenses/mit-license.html")
|
||||
@@ -285,4 +285,3 @@ jetbrainsLibrary("JPS")
|
||||
jetbrainsLibrary("Maven Embedder")
|
||||
jetbrainsLibrary("tcServiceMessages")
|
||||
jetbrainsLibrary("optimizedFileManager.jar")
|
||||
jetbrainsLibrary("fernflower.jar")
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
<orderEntry type="module" module-name="structuralsearch-java" />
|
||||
<orderEntry type="module" module-name="structuralsearch-tests" scope="TEST" />
|
||||
<orderEntry type="module" module-name="structuralsearch-groovy" />
|
||||
<orderEntry type="module" module-name="typeMigration" />
|
||||
</component>
|
||||
</module>
|
||||
|
||||
|
||||
@@ -4,6 +4,3 @@ org.jetbrains.idea.svn.*
|
||||
org.jetbrains.idea.svn16.*
|
||||
com.intellij.util.net.ssl.*
|
||||
com.intellij.tasks.integration.live.*
|
||||
|
||||
[DECOMPILER_TESTS]
|
||||
org.jetbrains.java.decompiler.*
|
||||
|
||||
@@ -55,7 +55,7 @@ import java.util.*;
|
||||
public class CompileContextImpl extends UserDataHolderBase implements CompileContextEx {
|
||||
private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.CompileContextImpl");
|
||||
private final Project myProject;
|
||||
private final CompilerTask myTask;
|
||||
private final CompilerTask myBuildSession;
|
||||
private final Map<CompilerMessageCategory, Collection<CompilerMessage>> myMessages = new EnumMap<CompilerMessageCategory, Collection<CompilerMessage>>(CompilerMessageCategory.class);
|
||||
private final boolean myShouldUpdateProblemsView;
|
||||
private CompileScope myCompileScope;
|
||||
@@ -77,7 +77,7 @@ public class CompileContextImpl extends UserDataHolderBase implements CompileCon
|
||||
CompileScope compileScope,
|
||||
boolean isMake, boolean isRebuild) {
|
||||
myProject = project;
|
||||
myTask = compilerSession;
|
||||
myBuildSession = compilerSession;
|
||||
myCompileScope = compileScope;
|
||||
myMake = isMake;
|
||||
myIsRebuild = isRebuild;
|
||||
@@ -99,6 +99,10 @@ public class CompileContextImpl extends UserDataHolderBase implements CompileCon
|
||||
myShouldUpdateProblemsView = workspaceConfig.MAKE_PROJECT_ON_SAVE;
|
||||
}
|
||||
|
||||
public CompilerTask getBuildSession() {
|
||||
return myBuildSession;
|
||||
}
|
||||
|
||||
public boolean shouldUpdateProblemsView() {
|
||||
return myShouldUpdateProblemsView;
|
||||
}
|
||||
@@ -154,7 +158,7 @@ public class CompileContextImpl extends UserDataHolderBase implements CompileCon
|
||||
myMessages.put(msg.getCategory(), messages);
|
||||
}
|
||||
if (messages.add(msg)) {
|
||||
myTask.addMessage(msg);
|
||||
myBuildSession.addMessage(msg);
|
||||
}
|
||||
if (myShouldUpdateProblemsView && msg.getCategory() == CompilerMessageCategory.ERROR) {
|
||||
ProblemsView.SERVICE.getInstance(myProject).addMessage(msg, mySessionId);
|
||||
@@ -200,7 +204,7 @@ public class CompileContextImpl extends UserDataHolderBase implements CompileCon
|
||||
}
|
||||
|
||||
public ProgressIndicator getProgressIndicator() {
|
||||
return myTask.getIndicator();
|
||||
return myBuildSession.getIndicator();
|
||||
}
|
||||
|
||||
public Module getModuleByFile(VirtualFile file) {
|
||||
|
||||
@@ -29,6 +29,8 @@ import com.intellij.compiler.ProblemsView;
|
||||
import com.intellij.compiler.progress.CompilerTask;
|
||||
import com.intellij.compiler.server.BuildManager;
|
||||
import com.intellij.compiler.server.DefaultMessageHandler;
|
||||
import com.intellij.notification.Notification;
|
||||
import com.intellij.notification.NotificationListener;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.compiler.*;
|
||||
import com.intellij.openapi.compiler.ex.CompilerPathsEx;
|
||||
@@ -53,10 +55,7 @@ import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFileManager;
|
||||
import com.intellij.openapi.wm.StatusBar;
|
||||
import com.intellij.openapi.wm.ToolWindowId;
|
||||
import com.intellij.openapi.wm.ToolWindowManager;
|
||||
import com.intellij.openapi.wm.WindowManager;
|
||||
import com.intellij.openapi.wm.*;
|
||||
import com.intellij.packaging.artifacts.Artifact;
|
||||
import com.intellij.packaging.impl.compiler.ArtifactCompilerUtil;
|
||||
import com.intellij.packaging.impl.compiler.ArtifactsCompiler;
|
||||
@@ -68,6 +67,7 @@ import com.intellij.util.ThrowableRunnable;
|
||||
import com.intellij.util.containers.HashMap;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.intellij.util.messages.MessageBus;
|
||||
import com.intellij.util.text.DateFormatUtil;
|
||||
import gnu.trove.THashSet;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -79,7 +79,9 @@ import org.jetbrains.jps.api.RequestFuture;
|
||||
import org.jetbrains.jps.model.java.JavaSourceRootType;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.HyperlinkEvent;
|
||||
import java.io.File;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -502,9 +504,24 @@ public class CompileDriver {
|
||||
if (duration > ONE_MINUTE_MS && CompilerWorkspaceConfiguration.getInstance(myProject).DISPLAY_NOTIFICATION_POPUP) {
|
||||
ToolWindowManager.getInstance(myProject).notifyByBalloon(ToolWindowId.MESSAGES_WINDOW, messageType, statusMessage);
|
||||
}
|
||||
CompilerManager.NOTIFICATION_GROUP.createNotification(statusMessage, messageType).notify(myProject);
|
||||
|
||||
final String wrappedMessage = _status != ExitStatus.UP_TO_DATE? "<a href='#'>" + statusMessage + "</a>" : statusMessage;
|
||||
final Notification notification = CompilerManager.NOTIFICATION_GROUP.createNotification(
|
||||
"", wrappedMessage,
|
||||
messageType.toNotificationType(),
|
||||
new MessagesActivationListener(compileContext)
|
||||
);
|
||||
compileContext.getBuildSession().registerCloseAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
notification.expire();
|
||||
}
|
||||
});
|
||||
notification.notify(myProject);
|
||||
|
||||
if (_status != ExitStatus.UP_TO_DATE && compileContext.getMessageCount(null) > 0) {
|
||||
compileContext.addMessage(CompilerMessageCategory.INFORMATION, statusMessage, null, -1, -1);
|
||||
final String msg = DateFormatUtil.formatDateTime(new Date()) + " - " + statusMessage;
|
||||
compileContext.addMessage(CompilerMessageCategory.INFORMATION, msg, null, -1, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -785,4 +802,28 @@ public class CompileDriver {
|
||||
private void showConfigurationDialog(String moduleNameToSelect, String tabNameToSelect) {
|
||||
ProjectSettingsService.getInstance(myProject).showModuleConfigurationDialog(moduleNameToSelect, tabNameToSelect);
|
||||
}
|
||||
|
||||
private static class MessagesActivationListener extends NotificationListener.Adapter {
|
||||
private final WeakReference<Project> myProjectRef;
|
||||
private final Object myContentId;
|
||||
|
||||
public MessagesActivationListener(CompileContextImpl compileContext) {
|
||||
myProjectRef = new WeakReference<Project>(compileContext.getProject());
|
||||
myContentId = compileContext.getBuildSession().getContentId();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void hyperlinkActivated(@NotNull Notification notification, @NotNull HyperlinkEvent e) {
|
||||
final Project project = myProjectRef.get();
|
||||
if (project != null && !project.isDisposed() && CompilerTask.showCompilerContent(project, myContentId)) {
|
||||
final ToolWindow tw = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.MESSAGES_WINDOW);
|
||||
if (tw != null) {
|
||||
tw.activate(null, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
notification.expire();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,11 @@
|
||||
package com.intellij.compiler.progress;
|
||||
|
||||
import com.intellij.compiler.CompilerManagerImpl;
|
||||
import com.intellij.compiler.CompilerMessageImpl;
|
||||
import com.intellij.compiler.impl.CompilerErrorTreeView;
|
||||
import com.intellij.ide.errorTreeView.NewErrorTreeViewPanel;
|
||||
import com.intellij.ide.errorTreeView.impl.ErrorTreeViewConfiguration;
|
||||
import com.intellij.ide.impl.ProjectUtil;
|
||||
import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.compiler.*;
|
||||
@@ -42,6 +41,7 @@ import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ProjectManager;
|
||||
import com.intellij.openapi.project.ProjectManagerListener;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
@@ -120,6 +120,26 @@ public class CompilerTask extends Task.Backgroundable {
|
||||
mySessionId = sessionId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Object getContentId() {
|
||||
return myContentId;
|
||||
}
|
||||
|
||||
public void registerCloseAction(final Runnable onClose) {
|
||||
synchronized (myMessageViewLock) {
|
||||
if (myErrorTreeView != null) {
|
||||
Disposer.register(myErrorTreeView, new Disposable() {
|
||||
@Override
|
||||
public void dispose() {
|
||||
onClose.run();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
onClose.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProcessId() {
|
||||
return "compilation";
|
||||
@@ -221,13 +241,15 @@ public class CompilerTask extends Task.Backgroundable {
|
||||
|
||||
private void addIndicatorDelegate() {
|
||||
ProgressIndicator indicator = myIndicator;
|
||||
if (!(indicator instanceof ProgressIndicatorEx)) return;
|
||||
if (!(indicator instanceof ProgressIndicatorEx)) {
|
||||
return;
|
||||
}
|
||||
((ProgressIndicatorEx)indicator).addStateDelegate(new ProgressIndicatorBase() {
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
super.cancel();
|
||||
closeUI();
|
||||
selectFirstMessage();
|
||||
stopAppIconProgress();
|
||||
}
|
||||
|
||||
@@ -235,11 +257,26 @@ public class CompilerTask extends Task.Backgroundable {
|
||||
public void stop() {
|
||||
super.stop();
|
||||
if (!isCanceled()) {
|
||||
closeUI();
|
||||
selectFirstMessage();
|
||||
}
|
||||
stopAppIconProgress();
|
||||
}
|
||||
|
||||
private void selectFirstMessage() {
|
||||
if (!isHeadlessMode()) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (myMessageViewLock) {
|
||||
if (myErrorTreeView != null) {
|
||||
myErrorTreeView.selectFirstMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void stopAppIconProgress() {
|
||||
UIUtil.invokeLaterIfNeeded(new Runnable() {
|
||||
@Override
|
||||
@@ -249,7 +286,8 @@ public class CompilerTask extends Task.Backgroundable {
|
||||
if (myErrorCount > 0) {
|
||||
appIcon.setErrorBadge(myProject, String.valueOf(myErrorCount));
|
||||
appIcon.requestAttention(myProject, true);
|
||||
} else if (!myCompilationStartedAutomatically) {
|
||||
}
|
||||
else if (!myCompilationStartedAutomatically) {
|
||||
appIcon.setOkBadge(myProject, true);
|
||||
appIcon.requestAttention(myProject, false);
|
||||
}
|
||||
@@ -450,18 +488,22 @@ public class CompilerTask extends Task.Backgroundable {
|
||||
public void showCompilerContent() {
|
||||
synchronized (myMessageViewLock) {
|
||||
if (myErrorTreeView != null) {
|
||||
final MessageView messageView = MessageView.SERVICE.getInstance(myProject);
|
||||
Content[] contents = messageView.getContentManager().getContents();
|
||||
for (Content content : contents) {
|
||||
if (CONTENT_ID_KEY.get(content) == myContentId) {
|
||||
messageView.getContentManager().setSelectedContent(content);
|
||||
return;
|
||||
}
|
||||
}
|
||||
showCompilerContent(myProject, myContentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean showCompilerContent(final Project project, final Object contentId) {
|
||||
final MessageView messageView = MessageView.SERVICE.getInstance(project);
|
||||
for (Content content : messageView.getContentManager().getContents()) {
|
||||
if (CONTENT_ID_KEY.get(content) == contentId) {
|
||||
messageView.getContentManager().setSelectedContent(content);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void removeAllContents(Project project, Content notRemove) {
|
||||
if (project.isDisposed()) {
|
||||
return;
|
||||
@@ -488,7 +530,7 @@ public class CompilerTask extends Task.Backgroundable {
|
||||
|
||||
private void activateMessageView() {
|
||||
synchronized (myMessageViewLock) {
|
||||
if (myErrorTreeView != null) {
|
||||
if (myErrorTreeView != null && myProject != null) {
|
||||
final ToolWindow tw = ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.MESSAGES_WINDOW);
|
||||
if (tw != null) {
|
||||
tw.activate(null, false);
|
||||
@@ -497,34 +539,6 @@ public class CompilerTask extends Task.Backgroundable {
|
||||
}
|
||||
}
|
||||
|
||||
private void closeUI() {
|
||||
if (isHeadlessMode()) {
|
||||
return;
|
||||
}
|
||||
Window window = getWindow();
|
||||
ModalityState modalityState = window != null ? ModalityState.stateForComponent(window) : ModalityState.NON_MODAL;
|
||||
final Application application = ApplicationManager.getApplication();
|
||||
application.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (myMessageViewLock) {
|
||||
if (myErrorTreeView != null) {
|
||||
final boolean shouldRetainView = myErrorCount > 0 || myWarningCount > 0 && !myErrorTreeView.isHideWarnings();
|
||||
if (shouldRetainView) {
|
||||
addMessage(new CompilerMessageImpl(myProject, CompilerMessageCategory.STATISTICS, CompilerBundle.message("statistics.error.count", myErrorCount)));
|
||||
addMessage(new CompilerMessageImpl(myProject, CompilerMessageCategory.STATISTICS, CompilerBundle.message("statistics.warnings.count", myWarningCount)));
|
||||
//activateMessageView();
|
||||
myErrorTreeView.selectFirstMessage();
|
||||
}
|
||||
else {
|
||||
removeAllContents(myProject, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, modalityState);
|
||||
}
|
||||
|
||||
public Window getWindow(){
|
||||
return null;
|
||||
}
|
||||
@@ -607,7 +621,7 @@ public class CompilerTask extends Task.Backgroundable {
|
||||
if (event.getContent() == myContent) {
|
||||
synchronized (myMessageViewLock) {
|
||||
if (myErrorTreeView != null) {
|
||||
myErrorTreeView.dispose();
|
||||
Disposer.dispose(myErrorTreeView);
|
||||
myErrorTreeView = null;
|
||||
if (myIndicator.isRunning()) {
|
||||
cancel();
|
||||
|
||||
@@ -34,7 +34,9 @@ import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.application.PathManager;
|
||||
import com.intellij.openapi.compiler.CompilationStatusListener;
|
||||
import com.intellij.openapi.compiler.CompileContext;
|
||||
import com.intellij.openapi.compiler.CompilerTopics;
|
||||
import com.intellij.openapi.components.ApplicationComponent;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.EditorFactory;
|
||||
@@ -64,16 +66,14 @@ import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.CharsetToolkit;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.vfs.VirtualFileManager;
|
||||
import com.intellij.openapi.vfs.newvfs.BulkFileListener;
|
||||
import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
|
||||
import com.intellij.openapi.vfs.newvfs.impl.FileNameCache;
|
||||
import com.intellij.openapi.wm.IdeFrame;
|
||||
import com.intellij.util.Alarm;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.PathUtil;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.*;
|
||||
import com.intellij.util.concurrency.Semaphore;
|
||||
import com.intellij.util.concurrency.SequentialTaskExecutor;
|
||||
import com.intellij.util.containers.IntArrayList;
|
||||
@@ -1117,6 +1117,44 @@ public class BuildManager implements ApplicationComponent{
|
||||
scheduleAutoMake();
|
||||
}
|
||||
});
|
||||
conn.subscribe(CompilerTopics.COMPILATION_STATUS, new CompilationStatusListener() {
|
||||
private final Set<String> myRootsToRefresh = new THashSet<String>(FileUtil.PATH_HASHING_STRATEGY);
|
||||
@Override
|
||||
public void compilationFinished(boolean aborted, int errors, int warnings, CompileContext compileContext) {
|
||||
final String[] roots;
|
||||
synchronized (myRootsToRefresh) {
|
||||
roots = ArrayUtil.toStringArray(myRootsToRefresh);
|
||||
myRootsToRefresh.clear();
|
||||
}
|
||||
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (project.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
|
||||
final LocalFileSystem lfs = LocalFileSystem.getInstance();
|
||||
final Set<VirtualFile> filesToRefresh = new HashSet<VirtualFile>();
|
||||
for (String root : roots) {
|
||||
final VirtualFile rootFile = lfs.refreshAndFindFileByPath(root);
|
||||
if (rootFile != null && fileIndex.isInSourceContent(rootFile)) {
|
||||
filesToRefresh.add(rootFile);
|
||||
}
|
||||
}
|
||||
if (!filesToRefresh.isEmpty()) {
|
||||
lfs.refreshFiles(filesToRefresh, true, true, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileGenerated(String outputRoot, String relativePath) {
|
||||
synchronized (myRootsToRefresh) {
|
||||
myRootsToRefresh.add(outputRoot);
|
||||
}
|
||||
}
|
||||
});
|
||||
final String projectPath = getProjectPath(project);
|
||||
Disposer.register(project, new Disposable() {
|
||||
@Override
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.util.ProgressIndicatorBase;
|
||||
import com.intellij.openapi.progress.util.ProgressIndicatorUtils;
|
||||
import com.intellij.openapi.progress.util.ReadTask;
|
||||
import com.intellij.openapi.project.DumbService;
|
||||
@@ -40,7 +41,6 @@ import org.jetbrains.jps.api.CmdlineRemoteProto;
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* @author Eugene Zhuravlev
|
||||
@@ -82,12 +82,7 @@ public abstract class DefaultMessageHandler implements BuilderMessageHandler {
|
||||
break;
|
||||
case CONSTANT_SEARCH_TASK:
|
||||
final CmdlineRemoteProto.Message.BuilderMessage.ConstantSearchTask task = msg.getConstantSearchTask();
|
||||
myTaskExecutor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleConstantSearchTask(channel, sessionId, task);
|
||||
}
|
||||
});
|
||||
handleConstantSearchTask(channel, sessionId, task);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -97,30 +92,29 @@ public abstract class DefaultMessageHandler implements BuilderMessageHandler {
|
||||
protected abstract void handleBuildEvent(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event);
|
||||
|
||||
private void handleConstantSearchTask(final Channel channel, final UUID sessionId, final CmdlineRemoteProto.Message.BuilderMessage.ConstantSearchTask task) {
|
||||
while (true) {
|
||||
final AtomicBoolean canceled = new AtomicBoolean(false);
|
||||
DumbService.getInstance(myProject).waitForSmartMode();
|
||||
ProgressIndicatorUtils.runWithWriteActionPriority(new ReadTask() {
|
||||
@Override
|
||||
public void computeInReadAction(@NotNull ProgressIndicator indicator) {
|
||||
if (DumbService.isDumb(myProject)) {
|
||||
canceled.set(true);
|
||||
return;
|
||||
}
|
||||
|
||||
ProgressIndicatorUtils.scheduleWithWriteActionPriority(new ProgressIndicatorBase(), myTaskExecutor, new ReadTask() {
|
||||
@Override
|
||||
public void computeInReadAction(@NotNull ProgressIndicator indicator) {
|
||||
if (DumbService.isDumb(myProject)) {
|
||||
onCanceled(indicator);
|
||||
}
|
||||
else {
|
||||
doHandleConstantSearchTask(channel, sessionId, task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled(@NotNull ProgressIndicator indicator) {
|
||||
canceled.set(true);
|
||||
}
|
||||
});
|
||||
if (!canceled.get()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled(@NotNull ProgressIndicator indicator) {
|
||||
DumbService.getInstance(myProject).runWhenSmart(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleConstantSearchTask(channel, sessionId, task);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void doHandleConstantSearchTask(Channel channel, UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.ConstantSearchTask task) {
|
||||
final String ownerClassName = task.getOwnerClassName();
|
||||
final String fieldName = task.getFieldName();
|
||||
|
||||
@@ -23,12 +23,10 @@ import com.intellij.debugger.ui.impl.watch.DebuggerTreeNodeImpl;
|
||||
import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl;
|
||||
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl;
|
||||
import com.intellij.debugger.ui.tree.render.*;
|
||||
import com.intellij.ide.actions.ShowSettingsUtilImpl;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.options.Configurable;
|
||||
import com.intellij.openapi.options.ex.SingleConfigurableEditor;
|
||||
import com.intellij.openapi.options.ShowSettingsUtil;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class AdjustArrayRangeAction extends DebuggerAction {
|
||||
@@ -67,30 +65,25 @@ public class AdjustArrayRangeAction extends DebuggerAction {
|
||||
title = title + " " + label.substring(index);
|
||||
}
|
||||
final ArrayRenderer clonedRenderer = renderer.clone();
|
||||
final NamedArrayConfigurable configurable = new NamedArrayConfigurable(title, clonedRenderer);
|
||||
SingleConfigurableEditor editor = new SingleConfigurableEditor(project, configurable,
|
||||
ShowSettingsUtilImpl.createDimensionKey(configurable), false);
|
||||
editor.show();
|
||||
|
||||
if(editor.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
|
||||
if (ShowSettingsUtil.getInstance().editConfigurable(project, new NamedArrayConfigurable(title, clonedRenderer))) {
|
||||
debugProcess.getManagerThread().schedule(new SuspendContextCommandImpl(debuggerContext.getSuspendContext()) {
|
||||
@Override
|
||||
public void contextAction() throws Exception {
|
||||
final ValueDescriptorImpl nodeDescriptor = (ValueDescriptorImpl)selectedNode.getDescriptor();
|
||||
final Renderer lastRenderer = nodeDescriptor.getLastRenderer();
|
||||
if (lastRenderer instanceof ArrayRenderer) {
|
||||
selectedNode.setRenderer(clonedRenderer);
|
||||
}
|
||||
else if (lastRenderer instanceof CompoundNodeRenderer) {
|
||||
final CompoundNodeRenderer compoundRenderer = (CompoundNodeRenderer)lastRenderer;
|
||||
final ChildrenRenderer childrenRenderer = compoundRenderer.getChildrenRenderer();
|
||||
if (childrenRenderer instanceof ExpressionChildrenRenderer) {
|
||||
ExpressionChildrenRenderer.setPreferableChildrenRenderer(nodeDescriptor, clonedRenderer);
|
||||
selectedNode.calcRepresentation();
|
||||
}
|
||||
@Override
|
||||
public void contextAction() throws Exception {
|
||||
final ValueDescriptorImpl nodeDescriptor = (ValueDescriptorImpl)selectedNode.getDescriptor();
|
||||
final Renderer lastRenderer = nodeDescriptor.getLastRenderer();
|
||||
if (lastRenderer instanceof ArrayRenderer) {
|
||||
selectedNode.setRenderer(clonedRenderer);
|
||||
}
|
||||
else if (lastRenderer instanceof CompoundNodeRenderer) {
|
||||
final CompoundNodeRenderer compoundRenderer = (CompoundNodeRenderer)lastRenderer;
|
||||
final ChildrenRenderer childrenRenderer = compoundRenderer.getChildrenRenderer();
|
||||
if (childrenRenderer instanceof ExpressionChildrenRenderer) {
|
||||
ExpressionChildrenRenderer.setPreferableChildrenRenderer(nodeDescriptor, clonedRenderer);
|
||||
selectedNode.calcRepresentation();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-12
@@ -19,23 +19,15 @@ import com.intellij.debugger.settings.ThreadsViewConfigurable;
|
||||
import com.intellij.debugger.settings.ThreadsViewSettings;
|
||||
import com.intellij.idea.ActionsBundle;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
import com.intellij.openapi.actionSystem.PlatformDataKeys;
|
||||
import com.intellij.openapi.options.ex.SingleConfigurableEditor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.options.ShowSettingsUtil;
|
||||
|
||||
/**
|
||||
* User: lex
|
||||
* Date: Sep 26, 2003
|
||||
* Time: 4:40:12 PM
|
||||
*/
|
||||
public class CustomizeThreadsViewAction extends DebuggerAction {
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
Project project = CommonDataKeys.PROJECT.getData(e.getDataContext());
|
||||
final SingleConfigurableEditor editor = new SingleConfigurableEditor(project, new ThreadsViewConfigurable(ThreadsViewSettings.getInstance()));
|
||||
editor.show();
|
||||
ShowSettingsUtil.getInstance().editConfigurable(e.getProject(), new ThreadsViewConfigurable(ThreadsViewSettings.getInstance()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(AnActionEvent e) {
|
||||
e.getPresentation().setVisible(true);
|
||||
e.getPresentation().setText(ActionsBundle.actionText(DebuggerActions.CUSTOMIZE_THREADS_VIEW));
|
||||
|
||||
+3
-2
@@ -71,7 +71,8 @@ public class JavaMarkObjectActionHandler extends MarkObjectActionHandler {
|
||||
|
||||
final DebuggerTree tree = node.getTree();
|
||||
tree.saveState(node);
|
||||
|
||||
|
||||
final Component parent = event.getInputEvent().getComponent();
|
||||
final ValueDescriptorImpl valueDescriptor = ((ValueDescriptorImpl)descriptor);
|
||||
final DebuggerContextImpl debuggerContext = tree.getDebuggerContext();
|
||||
final DebugProcessImpl debugProcess = debuggerContext.getDebugProcess();
|
||||
@@ -93,7 +94,7 @@ public class JavaMarkObjectActionHandler extends MarkObjectActionHandler {
|
||||
final boolean suggestAdditionalMarkup = canSuggestAdditionalMarkup(debugProcess, valueDescriptor.getValue());
|
||||
SwingUtilities.invokeAndWait(new Runnable() {
|
||||
public void run() {
|
||||
ObjectMarkupPropertiesDialog dialog = new ObjectMarkupPropertiesDialog(defaultText, suggestAdditionalMarkup);
|
||||
ObjectMarkupPropertiesDialog dialog = new ObjectMarkupPropertiesDialog(parent, defaultText, suggestAdditionalMarkup);
|
||||
dialog.show();
|
||||
if (dialog.isOK()) {
|
||||
result.set(Pair.create(dialog.getConfiguredMarkup(), dialog.isMarkAdditionalFields()));
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.debugger.actions;
|
||||
|
||||
import com.intellij.debugger.DebuggerContext;
|
||||
import com.intellij.debugger.engine.JavaValue;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
|
||||
import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
|
||||
import com.intellij.debugger.ui.impl.watch.FieldDescriptorImpl;
|
||||
import com.intellij.debugger.ui.impl.watch.NodeManagerImpl;
|
||||
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.xdebugger.frame.*;
|
||||
import com.intellij.xdebugger.frame.presentation.XValuePresentation;
|
||||
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodePresentationConfigurator;
|
||||
import com.sun.jdi.Field;
|
||||
import com.sun.jdi.ObjectReference;
|
||||
import com.sun.jdi.Value;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.List;
|
||||
|
||||
public class JavaReferringObjectsValue extends JavaValue {
|
||||
private static final long MAX_REFERRING = 100;
|
||||
private final boolean myIsField;
|
||||
|
||||
private JavaReferringObjectsValue(@Nullable JavaValue parent,
|
||||
@NotNull ValueDescriptorImpl valueDescriptor,
|
||||
@NotNull EvaluationContextImpl evaluationContext,
|
||||
NodeManagerImpl nodeManager,
|
||||
boolean isField) {
|
||||
super(parent, valueDescriptor, evaluationContext, nodeManager, false);
|
||||
myIsField = isField;
|
||||
}
|
||||
|
||||
public JavaReferringObjectsValue(@NotNull JavaValue javaValue, boolean isField) {
|
||||
super(null, javaValue.getDescriptor(), javaValue.getEvaluationContext(), null, false);
|
||||
myIsField = isField;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canNavigateToSource() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeChildren(@NotNull final XCompositeNode node) {
|
||||
if (checkContextNotResumed(node)) return;
|
||||
getEvaluationContext().getDebugProcess().getManagerThread().schedule(
|
||||
new SuspendContextCommandImpl(getEvaluationContext().getSuspendContext()) {
|
||||
@Override
|
||||
public Priority getPriority() {
|
||||
return Priority.NORMAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextAction() throws Exception {
|
||||
final XValueChildrenList children = new XValueChildrenList();
|
||||
|
||||
Value value = getDescriptor().getValue();
|
||||
List<ObjectReference> references = ((ObjectReference)value).referringObjects(MAX_REFERRING);
|
||||
int i = 1;
|
||||
for (final ObjectReference reference : references) {
|
||||
// try to find field name
|
||||
Field field = findField(reference, value);
|
||||
if (field != null) {
|
||||
ValueDescriptorImpl descriptor = new FieldDescriptorImpl(getProject(), reference, field) {
|
||||
@Override
|
||||
public Value calcValue(EvaluationContextImpl evaluationContext) throws EvaluateException {
|
||||
return reference;
|
||||
}
|
||||
};
|
||||
children.add(new JavaReferringObjectsValue(null, descriptor, getEvaluationContext(), null, true));
|
||||
i++;
|
||||
}
|
||||
else {
|
||||
ValueDescriptorImpl descriptor = new ValueDescriptorImpl(getProject(), reference) {
|
||||
@Override
|
||||
public Value calcValue(EvaluationContextImpl evaluationContext) throws EvaluateException {
|
||||
return reference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Ref";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String calcValueName() {
|
||||
return "Ref";
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiExpression getDescriptorEvaluation(DebuggerContext context) throws EvaluateException {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
children.add("Referrer " + i++, new JavaReferringObjectsValue(null, descriptor, getEvaluationContext(), null, false));
|
||||
}
|
||||
}
|
||||
|
||||
node.addChildren(children, true);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computePresentation(@NotNull final XValueNode node, @NotNull final XValuePlace place) {
|
||||
if (!myIsField) {
|
||||
super.computePresentation(node, place);
|
||||
}
|
||||
else {
|
||||
super.computePresentation(new XValueNodePresentationConfigurator.ConfigurableXValueNodeImpl() {
|
||||
@Override
|
||||
public void applyPresentation(@Nullable Icon icon, @NotNull final XValuePresentation valuePresenter, boolean hasChildren) {
|
||||
node.setPresentation(icon, new XValuePresentation() {
|
||||
@NotNull
|
||||
@Override
|
||||
public String getSeparator() {
|
||||
return " in ";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getType() {
|
||||
return valuePresenter.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderValue(@NotNull XValueTextRenderer renderer) {
|
||||
valuePresenter.renderValue(renderer);
|
||||
}
|
||||
}, hasChildren);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFullValueEvaluator(@NotNull XFullValueEvaluator fullValueEvaluator) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isObsolete() {
|
||||
return false;
|
||||
}
|
||||
}, place);
|
||||
}
|
||||
}
|
||||
|
||||
private static Field findField(ObjectReference reference, Value value) {
|
||||
for (Field field : reference.referenceType().allFields()) {
|
||||
if (reference.getValue(field) == value) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -20,6 +20,7 @@ import com.intellij.openapi.ui.ex.MultiLineLabel;
|
||||
import com.intellij.xdebugger.impl.ui.tree.ValueMarkerPresentationDialogBase;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
@@ -35,8 +36,8 @@ public class ObjectMarkupPropertiesDialog extends ValueMarkerPresentationDialogB
|
||||
private JPanel myAdditionalPropertiesPanel;
|
||||
private MultiLineLabel myDescriptionLabel;
|
||||
|
||||
public ObjectMarkupPropertiesDialog(@NotNull final String defaultText, boolean suggestAdditionalMarkup) {
|
||||
super(defaultText);
|
||||
public ObjectMarkupPropertiesDialog(@Nullable Component parent, @NotNull final String defaultText, boolean suggestAdditionalMarkup) {
|
||||
super(parent, defaultText);
|
||||
mySuggestAdditionalMarkup = suggestAdditionalMarkup;
|
||||
myDescriptionLabel.setText("If the value is referenced by a constant field of an abstract class,\n" +
|
||||
"IDEA could additionally mark all values referenced from this class with the names of referencing fields.");
|
||||
|
||||
@@ -19,68 +19,212 @@
|
||||
*/
|
||||
package com.intellij.debugger.actions;
|
||||
|
||||
import com.intellij.CommonBundle;
|
||||
import com.intellij.debugger.DebuggerBundle;
|
||||
import com.intellij.debugger.SourcePosition;
|
||||
import com.intellij.debugger.engine.DebugProcessImpl;
|
||||
import com.intellij.debugger.engine.JavaStackFrame;
|
||||
import com.intellij.debugger.engine.SuspendContextImpl;
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl;
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl;
|
||||
import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
|
||||
import com.intellij.debugger.ui.impl.watch.*;
|
||||
import com.intellij.debugger.settings.DebuggerSettings;
|
||||
import com.intellij.debugger.ui.impl.watch.DebuggerTreeNodeImpl;
|
||||
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl;
|
||||
import com.intellij.debugger.ui.impl.watch.ThreadDescriptorImpl;
|
||||
import com.intellij.idea.ActionsBundle;
|
||||
import com.intellij.openapi.actionSystem.ActionPlaces;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.ui.MessageDialogBuilder;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.psi.PsiCodeBlock;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiStatement;
|
||||
import com.intellij.psi.PsiTryStatement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.intellij.xdebugger.XDebugSession;
|
||||
import com.intellij.xdebugger.XDebuggerBundle;
|
||||
import com.intellij.xdebugger.XDebuggerManager;
|
||||
import com.intellij.xdebugger.evaluation.EvaluationMode;
|
||||
import com.intellij.xdebugger.evaluation.XDebuggerEvaluator;
|
||||
import com.intellij.xdebugger.frame.XStackFrame;
|
||||
import com.intellij.xdebugger.frame.XValue;
|
||||
import com.intellij.xdebugger.impl.breakpoints.XExpressionImpl;
|
||||
import com.sun.jdi.InvalidStackFrameException;
|
||||
import com.sun.jdi.NativeMethodException;
|
||||
import com.sun.jdi.VMDisconnectedException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PopFrameAction extends DebuggerAction {
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
Project project = e.getData(CommonDataKeys.PROJECT);
|
||||
StackFrameProxyImpl stackFrame = getStackFrameProxy(e);
|
||||
final Project project = e.getData(CommonDataKeys.PROJECT);
|
||||
final JavaStackFrame stackFrame = getStackFrame(e);
|
||||
if(stackFrame == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
DebuggerContextImpl debuggerContext = DebuggerAction.getDebuggerContext(e.getDataContext());
|
||||
DebugProcessImpl debugProcess = debuggerContext.getDebugProcess();
|
||||
final DebuggerContextImpl debuggerContext = DebuggerAction.getDebuggerContext(e.getDataContext());
|
||||
final DebugProcessImpl debugProcess = debuggerContext.getDebugProcess();
|
||||
if(debugProcess == null) {
|
||||
return;
|
||||
}
|
||||
debugProcess.getManagerThread().schedule(debugProcess.createPopFrameCommand(debuggerContext, stackFrame));
|
||||
|
||||
if (!DebuggerSettings.EVALUATE_FINALLY_NEVER.equals(DebuggerSettings.getInstance().EVALUATE_FINALLY_ON_POP_FRAME)) {
|
||||
List<PsiStatement> statements = getFinallyStatements(debuggerContext.getSourcePosition());
|
||||
if (!statements.isEmpty()) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (PsiStatement statement : statements) {
|
||||
sb.append("\n").append(statement.getText());
|
||||
}
|
||||
if (DebuggerSettings.EVALUATE_FINALLY_ALWAYS.equals(DebuggerSettings.getInstance().EVALUATE_FINALLY_ON_POP_FRAME)) {
|
||||
evaluateAndPop(project, stackFrame, debuggerContext, debugProcess, sb);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
int res = MessageDialogBuilder
|
||||
.yesNoCancel(UIUtil.removeMnemonic(ActionsBundle.actionText(DebuggerActions.POP_FRAME)),
|
||||
DebuggerBundle.message("warning.finally.block.detected") + sb)
|
||||
.project(project)
|
||||
.icon(Messages.getWarningIcon())
|
||||
.yesText(DebuggerBundle.message("button.execute.finally"))
|
||||
.noText(DebuggerBundle.message("button.drop.anyway"))
|
||||
.cancelText(CommonBundle.message("button.cancel"))
|
||||
.doNotAsk(
|
||||
new DialogWrapper.DoNotAskOption() {
|
||||
@Override
|
||||
public boolean isToBeShown() {
|
||||
return !DebuggerSettings.EVALUATE_FINALLY_ALWAYS.equals(DebuggerSettings.getInstance().EVALUATE_FINALLY_ON_POP_FRAME) &&
|
||||
!DebuggerSettings.EVALUATE_FINALLY_NEVER.equals(DebuggerSettings.getInstance().EVALUATE_FINALLY_ON_POP_FRAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setToBeShown(boolean value, int exitCode) {
|
||||
if (!value) {
|
||||
DebuggerSettings.getInstance().EVALUATE_FINALLY_ON_POP_FRAME =
|
||||
exitCode == Messages.YES ? DebuggerSettings.EVALUATE_FINALLY_ALWAYS : DebuggerSettings.EVALUATE_FINALLY_NEVER;
|
||||
}
|
||||
else {
|
||||
DebuggerSettings.getInstance().EVALUATE_FINALLY_ON_POP_FRAME = DebuggerSettings.EVALUATE_FINALLY_ASK;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeHidden() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSaveOptionsOnCancel() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getDoNotShowMessage() {
|
||||
return CommonBundle.message("dialog.options.do.not.show");
|
||||
}
|
||||
})
|
||||
.show();
|
||||
|
||||
switch (res) {
|
||||
case Messages.CANCEL:
|
||||
return;
|
||||
case Messages.NO:
|
||||
break;
|
||||
case Messages.YES: // evaluate finally
|
||||
evaluateAndPop(project, stackFrame, debuggerContext, debugProcess, sb);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
debugProcess.getManagerThread().schedule(debugProcess.createPopFrameCommand(debuggerContext, stackFrame.getStackFrameProxy()));
|
||||
}
|
||||
catch (NativeMethodException e2){
|
||||
Messages.showMessageDialog(project, DebuggerBundle.message("error.native.method.exception"), ActionsBundle.actionText(DebuggerActions.POP_FRAME), Messages.getErrorIcon());
|
||||
Messages.showMessageDialog(project, DebuggerBundle.message("error.native.method.exception"),
|
||||
UIUtil.removeMnemonic(ActionsBundle.actionText(DebuggerActions.POP_FRAME)), Messages.getErrorIcon());
|
||||
}
|
||||
catch (InvalidStackFrameException ignored) {
|
||||
}
|
||||
catch(VMDisconnectedException vde) {
|
||||
catch(VMDisconnectedException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static StackFrameProxyImpl getStackFrameProxy(AnActionEvent e) {
|
||||
DebuggerTreeNodeImpl selectedNode = getSelectedNode(e.getDataContext());
|
||||
if(selectedNode != null) {
|
||||
NodeDescriptorImpl descriptor = selectedNode.getDescriptor();
|
||||
if(descriptor instanceof StackFrameDescriptorImpl) {
|
||||
if(selectedNode.getNextSibling() != null) {
|
||||
StackFrameDescriptorImpl frameDescriptor = ((StackFrameDescriptorImpl)descriptor);
|
||||
return frameDescriptor.getFrameProxy();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
else if(descriptor instanceof ThreadDescriptorImpl || descriptor instanceof ThreadGroupDescriptorImpl) {
|
||||
return null;
|
||||
}
|
||||
private static void evaluateAndPop(final Project project,
|
||||
final JavaStackFrame stackFrame,
|
||||
final DebuggerContextImpl debuggerContext,
|
||||
final DebugProcessImpl debugProcess, StringBuilder sb) {
|
||||
XDebuggerEvaluator evaluator = stackFrame.getEvaluator();
|
||||
if (evaluator != null) {
|
||||
evaluator.evaluate(XExpressionImpl.fromText(sb.toString(), EvaluationMode.CODE_FRAGMENT),
|
||||
new XDebuggerEvaluator.XEvaluationCallback() {
|
||||
@Override
|
||||
public void evaluated(@NotNull XValue result) {
|
||||
debugProcess.getManagerThread()
|
||||
.schedule(debugProcess.createPopFrameCommand(debuggerContext, stackFrame.getStackFrameProxy()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void errorOccurred(@NotNull final String errorMessage) {
|
||||
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Messages
|
||||
.showMessageDialog(project, DebuggerBundle.message("error.executing.finally", errorMessage),
|
||||
UIUtil.removeMnemonic(ActionsBundle.actionText(DebuggerActions.POP_FRAME)),
|
||||
Messages.getErrorIcon());
|
||||
}
|
||||
});
|
||||
}
|
||||
}, stackFrame.getSourcePosition());
|
||||
}
|
||||
else {
|
||||
Messages.showMessageDialog(project, XDebuggerBundle.message("xdebugger.evaluate.stack.frame.has.not.evaluator"),
|
||||
UIUtil.removeMnemonic(ActionsBundle.actionText(DebuggerActions.POP_FRAME)),
|
||||
Messages.getErrorIcon());
|
||||
}
|
||||
}
|
||||
|
||||
private static List<PsiStatement> getFinallyStatements(SourcePosition position) {
|
||||
List<PsiStatement> res = new ArrayList<PsiStatement>();
|
||||
PsiElement element = position.getFile().findElementAt(position.getOffset());
|
||||
PsiTryStatement tryStatement = PsiTreeUtil.getParentOfType(element, PsiTryStatement.class);
|
||||
while (tryStatement != null) {
|
||||
PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock();
|
||||
if (finallyBlock != null) {
|
||||
ContainerUtil.addAll(res, finallyBlock.getStatements());
|
||||
}
|
||||
tryStatement = PsiTreeUtil.getParentOfType(tryStatement, PsiTryStatement.class);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static JavaStackFrame getStackFrame(AnActionEvent e) {
|
||||
//DebuggerTreeNodeImpl selectedNode = getSelectedNode(e.getDataContext());
|
||||
//if(selectedNode != null) {
|
||||
// NodeDescriptorImpl descriptor = selectedNode.getDescriptor();
|
||||
// if(descriptor instanceof StackFrameDescriptorImpl) {
|
||||
// if(selectedNode.getNextSibling() != null) {
|
||||
// StackFrameDescriptorImpl frameDescriptor = ((StackFrameDescriptorImpl)descriptor);
|
||||
// return frameDescriptor.getFrameProxy();
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
// else if(descriptor instanceof ThreadDescriptorImpl || descriptor instanceof ThreadGroupDescriptorImpl) {
|
||||
// return null;
|
||||
// }
|
||||
//}
|
||||
|
||||
Project project = e.getProject();
|
||||
if (project != null) {
|
||||
@@ -89,18 +233,19 @@ public class PopFrameAction extends DebuggerAction {
|
||||
XStackFrame frame = session.getCurrentStackFrame();
|
||||
if (frame instanceof JavaStackFrame) {
|
||||
StackFrameProxyImpl proxy = ((JavaStackFrame)frame).getStackFrameProxy();
|
||||
return !proxy.isBottom() ? proxy : null;
|
||||
return !proxy.isBottom() ? ((JavaStackFrame)frame) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebuggerContextImpl debuggerContext = DebuggerAction.getDebuggerContext(e.getDataContext());
|
||||
StackFrameProxyImpl frameProxy = debuggerContext.getFrameProxy();
|
||||
|
||||
if(frameProxy == null || frameProxy.isBottom()) {
|
||||
return null;
|
||||
}
|
||||
return frameProxy;
|
||||
//DebuggerContextImpl debuggerContext = DebuggerAction.getDebuggerContext(e.getDataContext());
|
||||
//StackFrameProxyImpl frameProxy = debuggerContext.getFrameProxy();
|
||||
//
|
||||
//if(frameProxy == null || frameProxy.isBottom()) {
|
||||
// return null;
|
||||
//}
|
||||
//return frameProxy;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isAtBreakpoint(AnActionEvent e) {
|
||||
@@ -118,10 +263,10 @@ public class PopFrameAction extends DebuggerAction {
|
||||
|
||||
public void update(AnActionEvent e) {
|
||||
boolean enable = false;
|
||||
StackFrameProxyImpl stackFrameProxy = getStackFrameProxy(e);
|
||||
JavaStackFrame stackFrame = getStackFrame(e);
|
||||
|
||||
if(stackFrameProxy != null && isAtBreakpoint(e)) {
|
||||
VirtualMachineProxyImpl virtualMachineProxy = stackFrameProxy.getVirtualMachine();
|
||||
if(stackFrame != null && isAtBreakpoint(e)) {
|
||||
VirtualMachineProxyImpl virtualMachineProxy = stackFrame.getStackFrameProxy().getVirtualMachine();
|
||||
enable = virtualMachineProxy.canPopFrames();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.debugger.actions;
|
||||
|
||||
import com.intellij.debugger.DebuggerContext;
|
||||
import com.intellij.debugger.engine.JavaValue;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
|
||||
import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
|
||||
import com.intellij.debugger.ui.impl.watch.NodeManagerImpl;
|
||||
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.xdebugger.frame.*;
|
||||
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree;
|
||||
import com.intellij.xdebugger.impl.ui.tree.XInspectDialog;
|
||||
import com.intellij.xdebugger.impl.ui.tree.actions.XDebuggerTreeActionBase;
|
||||
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
|
||||
import com.sun.jdi.ObjectReference;
|
||||
import com.sun.jdi.Value;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author egor
|
||||
*/
|
||||
public class ShowReferringObjectsAction extends XDebuggerTreeActionBase {
|
||||
private static final long MAX_REFERRING = 100;
|
||||
|
||||
@Override
|
||||
protected void perform(XValueNodeImpl node, @NotNull String nodeName, AnActionEvent e) {
|
||||
XValue container = node.getValueContainer();
|
||||
if (container instanceof JavaValue) {
|
||||
JavaValue javaValue = ((JavaValue)container);
|
||||
XDebuggerTree tree = XDebuggerTree.getTree(e.getDataContext());
|
||||
XInspectDialog dialog = new XInspectDialog(tree.getProject(),
|
||||
tree.getEditorsProvider(),
|
||||
tree.getSourcePosition(),
|
||||
nodeName,
|
||||
new ReferringObjectsValue(javaValue),
|
||||
tree.getValueMarkers());
|
||||
dialog.setTitle("Referring objects for " + nodeName);
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ReferringObjectsValue extends JavaValue {
|
||||
private ReferringObjectsValue(JavaValue parent,
|
||||
@NotNull ValueDescriptorImpl valueDescriptor,
|
||||
@NotNull EvaluationContextImpl evaluationContext,
|
||||
NodeManagerImpl nodeManager) {
|
||||
super(parent, valueDescriptor, evaluationContext, nodeManager);
|
||||
}
|
||||
|
||||
public ReferringObjectsValue(JavaValue javaValue) {
|
||||
super(null, javaValue.getDescriptor(), javaValue.getEvaluationContext(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canNavigateToSource() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeChildren(@NotNull final XCompositeNode node) {
|
||||
getEvaluationContext().getDebugProcess().getManagerThread().schedule(
|
||||
new SuspendContextCommandImpl(getEvaluationContext().getSuspendContext()) {
|
||||
@Override
|
||||
public Priority getPriority() {
|
||||
return Priority.NORMAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextAction() throws Exception {
|
||||
final XValueChildrenList children = new XValueChildrenList();
|
||||
|
||||
Value value = getDescriptor().getValue();
|
||||
List<ObjectReference> references = ((ObjectReference)value).referringObjects(MAX_REFERRING);
|
||||
int i = 1;
|
||||
for (final ObjectReference reference : references) {
|
||||
ValueDescriptorImpl descriptor = new ValueDescriptorImpl(getProject(), reference) {
|
||||
@Override
|
||||
public Value calcValue(EvaluationContextImpl evaluationContext) throws EvaluateException {
|
||||
return reference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Ref";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String calcValueName() {
|
||||
return "Ref";
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiExpression getDescriptorEvaluation(DebuggerContext context) throws EvaluateException {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
children.add("Referrer " + i++ , new ReferringObjectsValue(null, descriptor, getEvaluationContext(), null));
|
||||
}
|
||||
|
||||
node.addChildren(children, true);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -38,6 +38,7 @@ import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.util.DocumentUtil;
|
||||
import com.intellij.util.text.CharArrayUtil;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -66,7 +67,7 @@ public class ToggleMethodBreakpointAction extends AnAction {
|
||||
}
|
||||
final BreakpointManager manager = debugManager.getBreakpointManager();
|
||||
final PlaceInDocument place = getPlace(e);
|
||||
if(place != null) {
|
||||
if(place != null && DocumentUtil.isValidOffset(place.getOffset(), place.getDocument())) {
|
||||
Breakpoint breakpoint = manager.findBreakpoint(place.getDocument(), place.getOffset(), MethodBreakpoint.CATEGORY);
|
||||
if(breakpoint == null) {
|
||||
final int methodLine = place.getDocument().getLineNumber(place.getOffset());
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.intellij.debugger.engine;
|
||||
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
|
||||
import com.intellij.debugger.engine.evaluation.TextWithImportsImpl;
|
||||
import com.intellij.debugger.engine.events.DebuggerContextCommandImpl;
|
||||
@@ -73,10 +74,19 @@ public class JavaDebuggerEvaluator extends XDebuggerEvaluator {
|
||||
callback.errorOccurred("Context is not available");
|
||||
return;
|
||||
}
|
||||
JavaDebugProcess process = myDebugProcess.getXdebugProcess();
|
||||
if (process != null) {
|
||||
callback.evaluated(JavaValue.create(descriptor, evalContext, process.getNodeManager()));
|
||||
descriptor.setContext(evalContext);
|
||||
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
|
||||
EvaluateException exception = descriptor.getEvaluateException();
|
||||
if (exception != null) {
|
||||
callback.errorOccurred(exception.getMessage());
|
||||
return;
|
||||
}
|
||||
JavaDebugProcess process = myDebugProcess.getXdebugProcess();
|
||||
if (process == null) {
|
||||
callback.errorOccurred("No debug process");
|
||||
return;
|
||||
}
|
||||
callback.evaluated(JavaValue.create(null, descriptor, evalContext, process.getNodeManager(), true));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ package com.intellij.debugger.engine;
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException;
|
||||
import com.intellij.debugger.engine.events.DebuggerCommandImpl;
|
||||
import com.intellij.debugger.engine.events.DebuggerContextCommandImpl;
|
||||
import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx;
|
||||
@@ -46,7 +45,7 @@ public class JavaExecutionStack extends XExecutionStack {
|
||||
private final DebugProcessImpl myDebugProcess;
|
||||
private final NodeManagerImpl myNodeManager;
|
||||
private volatile JavaStackFrame myTopFrame;
|
||||
private boolean myTopFrameReady = false;
|
||||
private volatile boolean myTopFrameReady = false;
|
||||
private final MethodsTracker myTracker = new MethodsTracker();
|
||||
|
||||
public JavaExecutionStack(@NotNull ThreadReferenceProxyImpl threadProxy, @NotNull DebugProcessImpl debugProcess, boolean current) {
|
||||
@@ -99,25 +98,7 @@ public class JavaExecutionStack extends XExecutionStack {
|
||||
@Nullable
|
||||
@Override
|
||||
public JavaStackFrame getTopFrame() {
|
||||
if (!myTopFrameReady) {
|
||||
//TODO: remove sync calculation
|
||||
if (DebuggerManagerThreadImpl.isManagerThread()) {
|
||||
myTopFrame = calcTopFrame();
|
||||
}
|
||||
else {
|
||||
myDebugProcess.getManagerThread().invokeAndWait(new DebuggerCommandImpl() {
|
||||
@Override
|
||||
public Priority getPriority() {
|
||||
return Priority.HIGH;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void action() throws Exception {
|
||||
myTopFrame = calcTopFrame();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
assert myTopFrameReady : "Top frame must be already calculated here";
|
||||
return myTopFrame;
|
||||
}
|
||||
|
||||
@@ -183,8 +164,20 @@ public class JavaExecutionStack extends XExecutionStack {
|
||||
@Override
|
||||
public void contextAction() throws Exception {
|
||||
if (myStackFramesIterator.hasNext()) {
|
||||
JavaStackFrame frame = new JavaStackFrame(myStackFramesIterator.next(), myDebugProcess, myTracker, myNodeManager);
|
||||
if (DebuggerSettings.getInstance().SHOW_LIBRARY_STACKFRAMES || (!frame.getDescriptor().isSynthetic() && !frame.getDescriptor().isInLibraryContent())) {
|
||||
JavaStackFrame frame;
|
||||
boolean first = myAdded == 0;
|
||||
if (first && myTopFrameReady) {
|
||||
frame = myTopFrame;
|
||||
myStackFramesIterator.next();
|
||||
}
|
||||
else {
|
||||
frame = new JavaStackFrame(myStackFramesIterator.next(), myDebugProcess, myTracker, myNodeManager);
|
||||
if (first && !myTopFrameReady) {
|
||||
myTopFrame = frame;
|
||||
myTopFrameReady = true;
|
||||
}
|
||||
}
|
||||
if (first || DebuggerSettings.getInstance().SHOW_LIBRARY_STACKFRAMES || (!frame.getDescriptor().isSynthetic() && !frame.getDescriptor().isInLibraryContent())) {
|
||||
if (++myAdded > mySkip) {
|
||||
myContainer.addStackFrames(Arrays.asList(frame), false);
|
||||
}
|
||||
|
||||
@@ -370,7 +370,7 @@ public class JavaStackFrame extends XStackFrame {
|
||||
ArgumentValueDescriptorImpl descriptor = myNodeManager.getArgumentValueDescriptor(null, index, value, name);
|
||||
// setContext is required to calculate correct name
|
||||
descriptor.setContext(evaluationContext);
|
||||
return JavaValue.create(descriptor, evaluationContext, myNodeManager);
|
||||
return JavaValue.create(null, descriptor, evaluationContext, myNodeManager, true);
|
||||
}
|
||||
|
||||
protected void superBuildVariables(final EvaluationContextImpl evaluationContext, XValueChildrenList children) throws EvaluateException {
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
*/
|
||||
package com.intellij.debugger.engine;
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle;
|
||||
import com.intellij.debugger.DebuggerInvocationUtil;
|
||||
import com.intellij.debugger.SourcePosition;
|
||||
import com.intellij.debugger.actions.JavaReferringObjectsValue;
|
||||
import com.intellij.debugger.actions.JumpToObjectAction;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
|
||||
@@ -66,27 +68,34 @@ public class JavaValue extends XNamedValue implements NodeDescriptorProvider, XV
|
||||
private final ValueDescriptorImpl myValueDescriptor;
|
||||
private final EvaluationContextImpl myEvaluationContext;
|
||||
private final NodeManagerImpl myNodeManager;
|
||||
private final boolean myContextSet;
|
||||
|
||||
protected JavaValue(JavaValue parent,
|
||||
@NotNull ValueDescriptorImpl valueDescriptor,
|
||||
@NotNull EvaluationContextImpl evaluationContext,
|
||||
NodeManagerImpl nodeManager) {
|
||||
NodeManagerImpl nodeManager,
|
||||
boolean contextSet) {
|
||||
super(valueDescriptor.getName());
|
||||
myParent = parent;
|
||||
myValueDescriptor = valueDescriptor;
|
||||
myEvaluationContext = evaluationContext;
|
||||
myNodeManager = nodeManager;
|
||||
myContextSet = contextSet;
|
||||
}
|
||||
|
||||
private static JavaValue create(JavaValue parent, @NotNull ValueDescriptorImpl valueDescriptor, EvaluationContextImpl evaluationContext, NodeManagerImpl nodeManager, boolean init) {
|
||||
static JavaValue create(JavaValue parent,
|
||||
@NotNull ValueDescriptorImpl valueDescriptor,
|
||||
EvaluationContextImpl evaluationContext,
|
||||
NodeManagerImpl nodeManager,
|
||||
boolean contextSet) {
|
||||
DebuggerManagerThreadImpl.assertIsManagerThread();
|
||||
return new JavaValue(parent, valueDescriptor, evaluationContext, nodeManager);
|
||||
return new JavaValue(parent, valueDescriptor, evaluationContext, nodeManager, contextSet);
|
||||
}
|
||||
|
||||
static JavaValue create(@NotNull ValueDescriptorImpl valueDescriptor,
|
||||
EvaluationContextImpl evaluationContext,
|
||||
NodeManagerImpl nodeManager) {
|
||||
return create(null, valueDescriptor, evaluationContext, nodeManager, true);
|
||||
return create(null, valueDescriptor, evaluationContext, nodeManager, false);
|
||||
}
|
||||
|
||||
public JavaValue getParent() {
|
||||
@@ -113,7 +122,9 @@ public class JavaValue extends XNamedValue implements NodeDescriptorProvider, XV
|
||||
|
||||
@Override
|
||||
public void threadAction() {
|
||||
myValueDescriptor.setContext(myEvaluationContext);
|
||||
if (!myContextSet) {
|
||||
myValueDescriptor.setContext(myEvaluationContext);
|
||||
}
|
||||
myValueDescriptor.updateRepresentation(myEvaluationContext, new DescriptorLabelListener() {
|
||||
@Override
|
||||
public void labelChanged() {
|
||||
@@ -123,7 +134,7 @@ public class JavaValue extends XNamedValue implements NodeDescriptorProvider, XV
|
||||
String type = strings[0];
|
||||
XValuePresentation presentation;
|
||||
if (myValueDescriptor.isString()) {
|
||||
presentation = new TypedStringValuePresentation(StringUtil.unquoteString(value), type);
|
||||
presentation = new TypedStringValuePresentation(value, type);
|
||||
}
|
||||
else {
|
||||
EvaluateException exception = myValueDescriptor.getEvaluateException();
|
||||
@@ -263,7 +274,7 @@ public class JavaValue extends XNamedValue implements NodeDescriptorProvider, XV
|
||||
|
||||
@Override
|
||||
public void computeChildren(@NotNull final XCompositeNode node) {
|
||||
if (myEvaluationContext.getSuspendContext().isResumed()) return;
|
||||
if (checkContextNotResumed(node)) return;
|
||||
myEvaluationContext.getDebugProcess().getManagerThread().schedule(new SuspendContextCommandImpl(myEvaluationContext.getSuspendContext()) {
|
||||
@Override
|
||||
public Priority getPriority() {
|
||||
@@ -325,6 +336,14 @@ public class JavaValue extends XNamedValue implements NodeDescriptorProvider, XV
|
||||
});
|
||||
}
|
||||
|
||||
protected boolean checkContextNotResumed(XCompositeNode node) {
|
||||
if (myEvaluationContext.getSuspendContext().isResumed()) {
|
||||
node.setErrorMessage(DebuggerBundle.message("error.context.has.changed"));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeSourcePosition(@NotNull final XNavigatable navigatable) {
|
||||
if (myEvaluationContext.getSuspendContext().isResumed()) return;
|
||||
@@ -339,15 +358,16 @@ public class JavaValue extends XNamedValue implements NodeDescriptorProvider, XV
|
||||
ApplicationManager.getApplication().runReadAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final boolean nearest = navigatable instanceof XNearestSourcePosition;
|
||||
if (myValueDescriptor instanceof FieldDescriptorImpl) {
|
||||
SourcePosition position = ((FieldDescriptorImpl)myValueDescriptor).getSourcePosition(getProject(), getDebuggerContext());
|
||||
SourcePosition position = ((FieldDescriptorImpl)myValueDescriptor).getSourcePosition(getProject(), getDebuggerContext(), nearest);
|
||||
if (position != null) {
|
||||
navigatable.setSourcePosition(DebuggerUtilsEx.toXSourcePosition(position));
|
||||
}
|
||||
}
|
||||
if (myValueDescriptor instanceof LocalVariableDescriptorImpl) {
|
||||
SourcePosition position =
|
||||
((LocalVariableDescriptorImpl)myValueDescriptor).getSourcePosition(getProject(), getDebuggerContext());
|
||||
((LocalVariableDescriptorImpl)myValueDescriptor).getSourcePosition(getProject(), getDebuggerContext(), nearest);
|
||||
if (position != null) {
|
||||
navigatable.setSourcePosition(DebuggerUtilsEx.toXSourcePosition(position));
|
||||
}
|
||||
@@ -442,4 +462,14 @@ public class JavaValue extends XNamedValue implements NodeDescriptorProvider, XV
|
||||
public String getValueText() {
|
||||
return myValueDescriptor.getValueText();
|
||||
}
|
||||
@Nullable
|
||||
@Override
|
||||
public XReferrersProvider getReferrersProvider() {
|
||||
return new XReferrersProvider() {
|
||||
@Override
|
||||
public XValue getReferringObjectsValue() {
|
||||
return new JavaReferringObjectsValue(JavaValue.this, false);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
import com.intellij.openapi.progress.util.ProgressIndicatorListenerAdapter;
|
||||
import com.intellij.openapi.progress.util.ProgressWindowWithNotification;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.ui.SimpleColoredComponent;
|
||||
@@ -62,9 +63,12 @@ public class JavaValueModifier extends XValueModifier {
|
||||
@Override
|
||||
public String getInitialValueEditorText() {
|
||||
Value value = myJavaValue.getDescriptor().getValue();
|
||||
if (value instanceof PrimitiveValue || value instanceof StringReference) {
|
||||
if (value instanceof PrimitiveValue) {
|
||||
return myJavaValue.getValueString();
|
||||
}
|
||||
else if (value instanceof StringReference) {
|
||||
return StringUtil.wrapWithDoubleQuote(DebuggerUtils.translateStringValue(myJavaValue.getValueString()));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+6
-4
@@ -31,7 +31,7 @@ import com.intellij.debugger.engine.DebuggerUtils;
|
||||
import com.intellij.debugger.engine.JVMName;
|
||||
import com.intellij.debugger.engine.JVMNameUtil;
|
||||
import com.intellij.debugger.engine.evaluation.*;
|
||||
import com.intellij.debugger.ui.DebuggerEditorImpl;
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
@@ -40,6 +40,7 @@ import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiTypesUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.sun.jdi.Value;
|
||||
@@ -65,8 +66,8 @@ public class EvaluatorBuilderImpl implements EvaluatorBuilder {
|
||||
|
||||
final Project project = contextElement.getProject();
|
||||
|
||||
CodeFragmentFactory factory = DebuggerEditorImpl.findAppropriateFactory(text, contextElement);
|
||||
PsiCodeFragment codeFragment = new CodeFragmentFactoryContextWrapper(factory).createCodeFragment(text, contextElement, project);
|
||||
CodeFragmentFactory factory = DebuggerUtilsEx.findAppropriateCodeFragmentFactory(text, contextElement);
|
||||
PsiCodeFragment codeFragment = factory.createCodeFragment(text, contextElement, project);
|
||||
if (codeFragment == null) {
|
||||
throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.invalid.expression", text.getText()));
|
||||
}
|
||||
@@ -1036,7 +1037,8 @@ public class EvaluatorBuilderImpl implements EvaluatorBuilder {
|
||||
final PsiType castType = expression.getCastType().getType();
|
||||
final PsiType operandType = operandExpr.getType();
|
||||
|
||||
if (castType != null && operandType != null && !TypeConversionUtil.areTypesConvertible(operandType, castType)) {
|
||||
// if operand type can not be resolved in current context - leave it for runtime checks
|
||||
if (castType != null && operandType != null && !TypeConversionUtil.areTypesConvertible(operandType, castType) && PsiUtil.resolveClassInType(operandType) != null) {
|
||||
throw new EvaluateRuntimeException(
|
||||
new EvaluateException(JavaErrorMessages.message("inconvertible.type.cast", JavaHighlightUtil.formatType(operandType), JavaHighlightUtil
|
||||
.formatType(castType)))
|
||||
|
||||
+31
-9
@@ -21,21 +21,19 @@
|
||||
package com.intellij.debugger.engine.evaluation.expression;
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle;
|
||||
import com.intellij.debugger.engine.DebugProcess;
|
||||
import com.intellij.debugger.engine.DebugProcessImpl;
|
||||
import com.intellij.debugger.engine.DebuggerUtils;
|
||||
import com.intellij.debugger.engine.JVMName;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateRuntimeException;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
|
||||
import com.intellij.debugger.engine.evaluation.*;
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx;
|
||||
import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.sun.jdi.ClassType;
|
||||
import com.sun.jdi.Method;
|
||||
import com.sun.jdi.ObjectReference;
|
||||
import com.sun.jdi.ReferenceType;
|
||||
import com.intellij.rt.debugger.DefaultMethodInvoker;
|
||||
import com.sun.jdi.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class MethodEvaluator implements Evaluator {
|
||||
@@ -149,7 +147,13 @@ public class MethodEvaluator implements Evaluator {
|
||||
if (requiresSuperObject) {
|
||||
return debugProcess.invokeInstanceMethod(context, objRef, jdiMethod, args, ObjectReference.INVOKE_NONVIRTUAL);
|
||||
}
|
||||
return debugProcess.invokeMethod(context, objRef, jdiMethod, args);
|
||||
// fix for default methods in interfaces, see IDEA-124066
|
||||
if (Boolean.valueOf(System.getProperty("debugger.invoke.default")) && jdiMethod.declaringType() instanceof InterfaceType) {
|
||||
return invokeDefaultMethod(debugProcess, context, objRef, myMethodName);
|
||||
}
|
||||
else {
|
||||
return debugProcess.invokeMethod(context, objRef, jdiMethod, args);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
@@ -158,4 +162,22 @@ public class MethodEvaluator implements Evaluator {
|
||||
throw EvaluateExceptionUtil.createEvaluateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// only methods without arguments for now
|
||||
private static Value invokeDefaultMethod(DebugProcess debugProcess, EvaluationContext evaluationContext,
|
||||
Value obj, String name)
|
||||
throws EvaluateException, ClassNotLoadedException, InvalidTypeException {
|
||||
ClassType invokerClass = (ClassType)debugProcess.findClass(
|
||||
evaluationContext, DefaultMethodInvoker.class.getName(),
|
||||
evaluationContext.getClassLoader());
|
||||
|
||||
if (invokerClass != null) {
|
||||
List<Method> methods = invokerClass.methodsByName("invoke");
|
||||
if (!methods.isEmpty()) {
|
||||
return debugProcess.invokeMethod(evaluationContext, invokerClass, methods.get(0),
|
||||
Arrays.asList(obj, ((VirtualMachineProxyImpl)debugProcess.getVirtualMachineProxy()).mirrorOf(name)));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2009 JetBrains s.r.o.
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,13 +15,27 @@
|
||||
*/
|
||||
package com.intellij.debugger.impl;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.IdentifierHighlighterPass;
|
||||
import com.intellij.debugger.SourcePosition;
|
||||
import com.intellij.debugger.engine.SuspendContextImpl;
|
||||
import com.intellij.debugger.engine.SuspendManagerUtil;
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl;
|
||||
import com.intellij.debugger.ui.impl.watch.ThreadDescriptorImpl;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.util.Couple;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.util.PsiUtilBase;
|
||||
import com.intellij.xdebugger.XDebugSession;
|
||||
import com.intellij.xdebugger.XSourcePosition;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class DebuggerContextUtil {
|
||||
public static void setStackFrame(DebuggerStateManager manager, final StackFrameProxyImpl stackFrame) {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||
@@ -53,4 +67,43 @@ public class DebuggerContextUtil {
|
||||
public static DebuggerContextImpl createDebuggerContext(@NotNull DebuggerSession session, SuspendContextImpl suspendContext){
|
||||
return DebuggerContextImpl.createDebuggerContext(session, suspendContext, suspendContext != null ? suspendContext.getThread() : null, null);
|
||||
}
|
||||
|
||||
public static SourcePosition findNearest(@NotNull DebuggerContextImpl context, @NotNull PsiElement psi, @NotNull PsiFile file) {
|
||||
final DebuggerSession session = context.getDebuggerSession();
|
||||
if (session != null) {
|
||||
try {
|
||||
final XDebugSession debugSession = session.getXDebugSession();
|
||||
if (debugSession != null) {
|
||||
final XSourcePosition position = debugSession.getCurrentPosition();
|
||||
final Editor editor = PsiUtilBase.findEditor(psi);
|
||||
if (editor != null && position != null && file.getVirtualFile().equals(position.getFile())) {
|
||||
final Couple<Collection<TextRange>> usages = IdentifierHighlighterPass.getHighlightUsages(psi, file);
|
||||
final List<TextRange> ranges = new ArrayList<TextRange>();
|
||||
ranges.addAll(usages.first);
|
||||
ranges.addAll(usages.second);
|
||||
final int breakPointLine = position.getLine();
|
||||
int bestLine = -1;
|
||||
boolean hasSameLine = false;
|
||||
for (TextRange range : ranges) {
|
||||
final int line = editor.offsetToLogicalPosition(range.getStartOffset()).line;
|
||||
if (line > bestLine && line < breakPointLine) {
|
||||
bestLine = line;
|
||||
} else if (line == breakPointLine) {
|
||||
hasSameLine = true;
|
||||
}
|
||||
}
|
||||
if (bestLine > 0) {
|
||||
if (hasSameLine && breakPointLine - bestLine > 4) {
|
||||
return SourcePosition.createFromLine(file, breakPointLine);
|
||||
}
|
||||
return SourcePosition.createFromLine(file, bestLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
return SourcePosition.createFromOffset(file, psi.getTextOffset());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.fileTypes.FileType;
|
||||
import com.intellij.openapi.fileTypes.StdFileTypes;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.*;
|
||||
@@ -403,17 +404,25 @@ public abstract class DebuggerUtilsEx extends DebuggerUtils {
|
||||
|
||||
public abstract CompletionEditor createEditor(Project project, PsiElement context, @NonNls String recentsId);
|
||||
|
||||
@Nullable
|
||||
public static CodeFragmentFactory getEffectiveCodeFragmentFactory(final PsiElement psiContext) {
|
||||
final CodeFragmentFactory factory = ApplicationManager.getApplication().runReadAction(new Computable<CodeFragmentFactory>() {
|
||||
@NotNull
|
||||
public static CodeFragmentFactory findAppropriateCodeFragmentFactory(final TextWithImports text, final PsiElement context) {
|
||||
CodeFragmentFactory factory = ApplicationManager.getApplication().runReadAction(new Computable<CodeFragmentFactory>() {
|
||||
@Override
|
||||
public CodeFragmentFactory compute() {
|
||||
final List<CodeFragmentFactory> codeFragmentFactories = getCodeFragmentFactories(psiContext);
|
||||
// the list always contains at least DefaultCodeFragmentFactory
|
||||
return codeFragmentFactories.get(0);
|
||||
final FileType fileType = text.getFileType();
|
||||
final List<CodeFragmentFactory> factories = getCodeFragmentFactories(context);
|
||||
if (fileType == null) {
|
||||
return factories.get(0);
|
||||
}
|
||||
for (CodeFragmentFactory factory : factories) {
|
||||
if (factory.getFileType().equals(fileType)) {
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
return DefaultCodeFragmentFactory.getInstance();
|
||||
}
|
||||
});
|
||||
return factory != null? new CodeFragmentFactoryContextWrapper(factory) : null;
|
||||
return new CodeFragmentFactoryContextWrapper(factory);
|
||||
}
|
||||
|
||||
private static class SigReader {
|
||||
|
||||
@@ -86,6 +86,10 @@ public class JavaEditorTextProviderImpl implements EditorTextProvider {
|
||||
public Pair<PsiElement, TextRange> findExpression(PsiElement element, boolean allowMethodCalls) {
|
||||
PsiElement expression = null;
|
||||
PsiElement parent = element.getParent();
|
||||
if (parent instanceof PsiLiteralExpression) {
|
||||
element = parent;
|
||||
parent = parent.getParent();
|
||||
}
|
||||
if (parent instanceof PsiVariable) {
|
||||
expression = element;
|
||||
}
|
||||
@@ -107,12 +111,7 @@ public class JavaEditorTextProviderImpl implements EditorTextProvider {
|
||||
else if (parent instanceof PsiThisExpression) {
|
||||
expression = parent;
|
||||
}
|
||||
else if (parent instanceof PsiInstanceOfExpression
|
||||
|| parent instanceof PsiBinaryExpression
|
||||
|| parent instanceof PsiPolyadicExpression
|
||||
|| parent instanceof PsiPrefixExpression
|
||||
|| parent instanceof PsiConditionalExpression
|
||||
) {
|
||||
else if (parent instanceof PsiExpression && !(parent instanceof PsiNewExpression)) {
|
||||
if (allowMethodCalls || !DebuggerUtils.hasSideEffects(parent)) {
|
||||
expression = parent;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ public class PositionUtil extends ContextUtil {
|
||||
return ContextUtil.getSourcePosition(context);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiElement getContextElement(final StackFrameContext context) {
|
||||
if(context instanceof DebuggerContextImpl) return ((DebuggerContextImpl) context).getContextElement();
|
||||
|
||||
|
||||
+16
-15
@@ -53,7 +53,7 @@ public class DebuggerDataViewsConfigurable implements SearchableConfigurable {
|
||||
private JCheckBox myCbShowObjectId;
|
||||
|
||||
private StateRestoringCheckBox myCbShowStaticFinalFields;
|
||||
private final ArrayRendererConfigurable myArrayRendererConfigurable;
|
||||
//private final ArrayRendererConfigurable myArrayRendererConfigurable;
|
||||
private JCheckBox myCbEnableAlternateViews;
|
||||
|
||||
private JCheckBox myCbEnableToString;
|
||||
@@ -66,12 +66,12 @@ public class DebuggerDataViewsConfigurable implements SearchableConfigurable {
|
||||
|
||||
public DebuggerDataViewsConfigurable(@Nullable Project project) {
|
||||
myProject = project;
|
||||
myArrayRendererConfigurable = new ArrayRendererConfigurable(NodeRendererSettings.getInstance().getArrayRenderer());
|
||||
//myArrayRendererConfigurable = new ArrayRendererConfigurable(NodeRendererSettings.getInstance().getArrayRenderer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disposeUIResources() {
|
||||
myArrayRendererConfigurable.disposeUIResources();
|
||||
//myArrayRendererConfigurable.disposeUIResources();
|
||||
myToStringFilterEditor = null;
|
||||
myProject = null;
|
||||
}
|
||||
@@ -165,13 +165,14 @@ public class DebuggerDataViewsConfigurable implements SearchableConfigurable {
|
||||
|
||||
panel.add(showPanel, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 3, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0));
|
||||
|
||||
final JPanel arraysPanel = new JPanel(new BorderLayout(0, UIUtil.DEFAULT_VGAP));
|
||||
final JComponent arraysComponent = myArrayRendererConfigurable.createComponent();
|
||||
assert arraysComponent != null;
|
||||
arraysPanel.add(arraysComponent, BorderLayout.CENTER);
|
||||
arraysPanel.add(myCbHideNullArrayElements, BorderLayout.SOUTH);
|
||||
arraysPanel.setBorder(IdeBorderFactory.createTitledBorder("Arrays", true));
|
||||
panel.add(arraysPanel, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 3, 1, 1.0, 0.0, GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
|
||||
//final JPanel arraysPanel = new JPanel(new BorderLayout(0, UIUtil.DEFAULT_VGAP));
|
||||
//final JComponent arraysComponent = myArrayRendererConfigurable.createComponent();
|
||||
//assert arraysComponent != null;
|
||||
//arraysPanel.add(arraysComponent, BorderLayout.CENTER);
|
||||
//arraysPanel.add(myCbHideNullArrayElements, BorderLayout.SOUTH);
|
||||
//arraysPanel.setBorder(IdeBorderFactory.createTitledBorder("Arrays", true));
|
||||
//panel.add(arraysPanel, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 3, 1, 1.0, 0.0, GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
|
||||
panel.add(myCbHideNullArrayElements, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 3, 1, 1.0, 0.0, GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
|
||||
|
||||
panel.add(myCbEnableAlternateViews, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(4, 0, 0, 10), 0, 0));
|
||||
// starting 4-th row
|
||||
@@ -209,7 +210,7 @@ public class DebuggerDataViewsConfigurable implements SearchableConfigurable {
|
||||
|
||||
myAutoTooltip.save();
|
||||
|
||||
myArrayRendererConfigurable.apply();
|
||||
//myArrayRendererConfigurable.apply();
|
||||
|
||||
rendererSettings.fireRenderersChanged();
|
||||
}
|
||||
@@ -250,7 +251,7 @@ public class DebuggerDataViewsConfigurable implements SearchableConfigurable {
|
||||
myRbFromList.setEnabled(toStringEnabled);
|
||||
myRbAllThatOverride.setEnabled(toStringEnabled);
|
||||
|
||||
myArrayRendererConfigurable.reset();
|
||||
//myArrayRendererConfigurable.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -266,9 +267,9 @@ public class DebuggerDataViewsConfigurable implements SearchableConfigurable {
|
||||
}
|
||||
|
||||
private boolean areDefaultRenderersModified() {
|
||||
if (myArrayRendererConfigurable.isModified()) {
|
||||
return true;
|
||||
}
|
||||
//if (myArrayRendererConfigurable.isModified()) {
|
||||
// return true;
|
||||
//}
|
||||
|
||||
final NodeRendererSettings rendererSettings = NodeRendererSettings.getInstance();
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.intellij.openapi.components.*;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.InvalidDataException;
|
||||
import com.intellij.openapi.util.WriteExternalException;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.ui.classFilter.ClassFilter;
|
||||
import com.intellij.util.containers.hash.LinkedHashMap;
|
||||
import com.intellij.util.xmlb.SkipDefaultValuesSerializationFilters;
|
||||
@@ -55,6 +56,10 @@ public class DebuggerSettings implements Cloneable, PersistentStateComponent<Ele
|
||||
@NonNls public static final String RUN_HOTSWAP_NEVER = "RunHotswapNever";
|
||||
@NonNls public static final String RUN_HOTSWAP_ASK = "RunHotswapAsk";
|
||||
|
||||
@NonNls public static final String EVALUATE_FINALLY_ALWAYS = "EvaluateFinallyAlways";
|
||||
@NonNls public static final String EVALUATE_FINALLY_NEVER = "EvaluateFinallyNever";
|
||||
@NonNls public static final String EVALUATE_FINALLY_ASK = "EvaluateFinallyAsk";
|
||||
|
||||
public boolean TRACING_FILTERS_ENABLED;
|
||||
public int DEBUGGER_TRANSPORT;
|
||||
public boolean FORCE_CLASSIC_VM;
|
||||
@@ -74,6 +79,8 @@ public class DebuggerSettings implements Cloneable, PersistentStateComponent<Ele
|
||||
public volatile boolean AUTO_VARIABLES_MODE = false;
|
||||
public volatile boolean SHOW_LIBRARY_STACKFRAMES = true;
|
||||
|
||||
public String EVALUATE_FINALLY_ON_POP_FRAME = EVALUATE_FINALLY_ASK;
|
||||
|
||||
private ClassFilter[] mySteppingFilters = ClassFilter.EMPTY_ARRAY;
|
||||
|
||||
private Map<String, ContentState> myContentStates = new LinkedHashMap<String, ContentState>();
|
||||
@@ -142,6 +149,7 @@ public class DebuggerSettings implements Cloneable, PersistentStateComponent<Ele
|
||||
return
|
||||
TRACING_FILTERS_ENABLED == secondSettings.TRACING_FILTERS_ENABLED &&
|
||||
DEBUGGER_TRANSPORT == secondSettings.DEBUGGER_TRANSPORT &&
|
||||
StringUtil.equals(EVALUATE_FINALLY_ON_POP_FRAME, secondSettings.EVALUATE_FINALLY_ON_POP_FRAME) &&
|
||||
FORCE_CLASSIC_VM == secondSettings.FORCE_CLASSIC_VM &&
|
||||
DISABLE_JIT == secondSettings.DISABLE_JIT &&
|
||||
HOTSWAP_IN_BACKGROUND == secondSettings.HOTSWAP_IN_BACKGROUND &&
|
||||
|
||||
+51
@@ -22,6 +22,7 @@ import com.intellij.ui.classFilter.ClassFilterEditor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
@@ -33,6 +34,9 @@ class DebuggerSteppingConfigurable implements ConfigurableUi<DebuggerSettings> {
|
||||
private JCheckBox myCbSkipClassLoaders;
|
||||
private ClassFilterEditor mySteppingFilterEditor;
|
||||
private JCheckBox myCbSkipSimpleGetters;
|
||||
private JRadioButton myRbEvaluateFinallyAlways;
|
||||
private JRadioButton myRbEvaluateFinallyNever;
|
||||
private JRadioButton myRbEvaluateFinallyAsk;
|
||||
|
||||
@Override
|
||||
public void reset(@NotNull DebuggerSettings settings) {
|
||||
@@ -45,6 +49,16 @@ class DebuggerSteppingConfigurable implements ConfigurableUi<DebuggerSettings> {
|
||||
|
||||
mySteppingFilterEditor.setFilters(settings.getSteppingFilters());
|
||||
mySteppingFilterEditor.setEnabled(settings.TRACING_FILTERS_ENABLED);
|
||||
|
||||
if (DebuggerSettings.EVALUATE_FINALLY_ALWAYS.equals(settings.EVALUATE_FINALLY_ON_POP_FRAME)) {
|
||||
myRbEvaluateFinallyAlways.setSelected(true);
|
||||
}
|
||||
else if (DebuggerSettings.EVALUATE_FINALLY_NEVER.equals(settings.EVALUATE_FINALLY_ON_POP_FRAME)) {
|
||||
myRbEvaluateFinallyNever.setSelected(true);
|
||||
}
|
||||
else {
|
||||
myRbEvaluateFinallyAsk.setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,6 +73,16 @@ class DebuggerSteppingConfigurable implements ConfigurableUi<DebuggerSettings> {
|
||||
settings.SKIP_CLASSLOADERS = myCbSkipClassLoaders.isSelected();
|
||||
settings.TRACING_FILTERS_ENABLED = myCbStepInfoFiltersEnabled.isSelected();
|
||||
|
||||
if (myRbEvaluateFinallyAlways.isSelected()) {
|
||||
settings.EVALUATE_FINALLY_ON_POP_FRAME = DebuggerSettings.EVALUATE_FINALLY_ALWAYS;
|
||||
}
|
||||
else if (myRbEvaluateFinallyNever.isSelected()) {
|
||||
settings.EVALUATE_FINALLY_ON_POP_FRAME = DebuggerSettings.EVALUATE_FINALLY_NEVER;
|
||||
}
|
||||
else {
|
||||
settings.EVALUATE_FINALLY_ON_POP_FRAME = DebuggerSettings.EVALUATE_FINALLY_ASK;
|
||||
}
|
||||
|
||||
mySteppingFilterEditor.stopEditing();
|
||||
settings.setSteppingFilters(mySteppingFilterEditor.getFilters());
|
||||
}
|
||||
@@ -94,6 +118,33 @@ class DebuggerSteppingConfigurable implements ConfigurableUi<DebuggerSettings> {
|
||||
mySteppingFilterEditor.setEnabled(myCbStepInfoFiltersEnabled.isSelected());
|
||||
}
|
||||
});
|
||||
|
||||
myRbEvaluateFinallyAlways = new JRadioButton(DebuggerBundle.message("label.debugger.general.configurable.evaluate.finally.always"));
|
||||
myRbEvaluateFinallyNever = new JRadioButton(DebuggerBundle.message("label.debugger.general.configurable.evaluate.finally.never"));
|
||||
myRbEvaluateFinallyAsk = new JRadioButton(DebuggerBundle.message("label.debugger.general.configurable.evaluate.finally.ask"));
|
||||
|
||||
int cbLeftOffset = 0;
|
||||
final Border border = myCbSkipSimpleGetters.getBorder();
|
||||
if (border != null) {
|
||||
final Insets insets = border.getBorderInsets(myCbSkipSimpleGetters);
|
||||
if (insets != null) {
|
||||
cbLeftOffset = insets.left;
|
||||
}
|
||||
}
|
||||
|
||||
final ButtonGroup group = new ButtonGroup();
|
||||
group.add(myRbEvaluateFinallyAlways);
|
||||
group.add(myRbEvaluateFinallyNever);
|
||||
group.add(myRbEvaluateFinallyAsk);
|
||||
final Box box = Box.createHorizontalBox();
|
||||
box.add(myRbEvaluateFinallyAlways);
|
||||
box.add(myRbEvaluateFinallyNever);
|
||||
box.add(myRbEvaluateFinallyAsk);
|
||||
final JPanel evalFinallyPanel = new JPanel(new BorderLayout());
|
||||
evalFinallyPanel.add(box, BorderLayout.CENTER);
|
||||
evalFinallyPanel.add(new JLabel(DebuggerBundle.message("label.debugger.general.configurable.evaluate.finally.on.pop")), BorderLayout.WEST);
|
||||
panel.add(evalFinallyPanel, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(4, cbLeftOffset, 0, 0), 0, 0));
|
||||
|
||||
return panel;
|
||||
}
|
||||
}
|
||||
@@ -294,7 +294,7 @@ public abstract class DebuggerEditorImpl extends CompletionEditor{
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static CodeFragmentFactory findAppropriateFactory(@NotNull TextWithImports text, @NotNull PsiElement context) {
|
||||
private static CodeFragmentFactory findAppropriateFactory(@NotNull TextWithImports text, @Nullable PsiElement context) {
|
||||
for (CodeFragmentFactory factory : DebuggerUtilsEx.getCodeFragmentFactories(context)) {
|
||||
if (factory.getFileType().equals(text.getFileType())) {
|
||||
return factory;
|
||||
|
||||
@@ -30,7 +30,6 @@ import com.intellij.debugger.ui.impl.ValueNodeDnD;
|
||||
import com.intellij.debugger.ui.impl.WatchDebuggerTree;
|
||||
import com.intellij.debugger.ui.impl.WatchPanel;
|
||||
import com.intellij.debugger.ui.impl.watch.DebuggerTreeNodeImpl;
|
||||
import com.intellij.debugger.ui.impl.watch.EvaluationDescriptor;
|
||||
import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.actionSystem.*;
|
||||
@@ -110,10 +109,10 @@ public abstract class EvaluationDialog extends DialogWrapper {
|
||||
try {
|
||||
setOKActionEnabled(false);
|
||||
NodeDescriptorImpl descriptor = myEvaluationPanel.getWatchTree().addWatch(codeToEvaluate, "result").getDescriptor();
|
||||
if (descriptor instanceof EvaluationDescriptor) {
|
||||
final EvaluationDescriptor evalDescriptor = (EvaluationDescriptor)descriptor;
|
||||
evalDescriptor.setCodeFragmentFactory(myEditor.getCurrentFactory());
|
||||
}
|
||||
//if (descriptor instanceof EvaluationDescriptor) {
|
||||
// final EvaluationDescriptor evalDescriptor = (EvaluationDescriptor)descriptor;
|
||||
// evalDescriptor.setCodeFragmentFactory(myEditor.getCurrentFactory());
|
||||
//}
|
||||
myEvaluationPanel.getWatchTree().rebuild(getDebuggerContext());
|
||||
descriptor.myIsExpanded = true;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class HotSwapProgressImpl extends HotSwapProgress{
|
||||
static final NotificationGroup NOTIFICATION_GROUP = NotificationGroup.toolWindowGroup("HotSwap", ToolWindowId.DEBUG, true);
|
||||
static final NotificationGroup NOTIFICATION_GROUP = NotificationGroup.toolWindowGroup("HotSwap", ToolWindowId.DEBUG);
|
||||
|
||||
TIntObjectHashMap<List<String>> myMessages = new TIntObjectHashMap<List<String>>();
|
||||
private final ProgressWindow myProgressWindow;
|
||||
|
||||
@@ -94,9 +94,8 @@ public class ValueHint extends AbstractValueHint {
|
||||
return EvaluatorBuilderImpl.getInstance().build(myCurrentExpression, debuggerContext.getSourcePosition());
|
||||
}
|
||||
|
||||
CodeFragmentFactory factory = DebuggerUtilsEx.getEffectiveCodeFragmentFactory(myCurrentExpression);
|
||||
TextWithImportsImpl textWithImports = new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, myCurrentExpression.getText());
|
||||
if (factory == null) return null;
|
||||
CodeFragmentFactory factory = DebuggerUtilsEx.findAppropriateCodeFragmentFactory(textWithImports, myCurrentExpression);
|
||||
JavaCodeFragment codeFragment = factory.createCodeFragment(textWithImports, myCurrentExpression.getContext(), getProject());
|
||||
codeFragment.forceResolveScope(GlobalSearchScope.allScope(getProject()));
|
||||
return factory.getEvaluatorBuilder().build(codeFragment, debuggerContext.getSourcePosition());
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.debugger.ui.impl.watch;
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcess;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContext;
|
||||
import com.intellij.debugger.engine.evaluation.TextWithImports;
|
||||
import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator;
|
||||
import com.intellij.debugger.engine.evaluation.expression.Modifier;
|
||||
import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
|
||||
import com.sun.jdi.*;
|
||||
|
||||
import javax.tools.*;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author egor
|
||||
*/
|
||||
class CompilingEvaluator implements ExpressionEvaluator {
|
||||
private final TextWithImports myText;
|
||||
|
||||
public CompilingEvaluator(TextWithImports text) {
|
||||
myText = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value getValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modifier getModifier() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value evaluate(EvaluationContext context) throws EvaluateException {
|
||||
try {
|
||||
DebugProcess process = context.getDebugProcess();
|
||||
ThreadReference threadReference = context.getSuspendContext().getThread().getThreadReference();
|
||||
|
||||
ClassLoaderReference classLoader = getClassLoader(context);
|
||||
|
||||
Collection<OutputFileObject> classes = compile();
|
||||
|
||||
ClassType mainClass = defineClasses(classes, context, process, threadReference, classLoader);
|
||||
|
||||
Method foo = mainClass.methodsByName(GEN_METHOD_NAME).get(0);
|
||||
return mainClass.invokeMethod(threadReference, foo, Collections.<Value>emptyList() ,ClassType.INVOKE_SINGLE_THREADED);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new EvaluateException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static ClassLoaderReference getClassLoader(EvaluationContext context)
|
||||
throws EvaluateException, InvocationException, InvalidTypeException, ClassNotLoadedException, IncompatibleThreadStateException {
|
||||
// TODO: cache
|
||||
DebugProcess process = context.getDebugProcess();
|
||||
ClassType loaderClass = (ClassType)process.findClass(context, "java.net.URLClassLoader", context.getClassLoader());
|
||||
Method ctorMethod = loaderClass.concreteMethodByName("<init>", "([Ljava/net/URL;)V");
|
||||
ThreadReference threadReference = context.getSuspendContext().getThread().getThreadReference();
|
||||
return (ClassLoaderReference)loaderClass.newInstance(threadReference, ctorMethod,
|
||||
Arrays.asList(createURLArray(context)), ClassType.INVOKE_SINGLE_THREADED);
|
||||
}
|
||||
|
||||
private static ClassType defineClasses(Collection<OutputFileObject> classes,
|
||||
EvaluationContext context,
|
||||
DebugProcess process,
|
||||
ThreadReference threadReference,
|
||||
ClassLoaderReference classLoader)
|
||||
throws EvaluateException, InvalidTypeException, ClassNotLoadedException, IncompatibleThreadStateException, InvocationException {
|
||||
|
||||
VirtualMachineProxyImpl proxy = (VirtualMachineProxyImpl)process.getVirtualMachineProxy();
|
||||
for (OutputFileObject cls : classes) {
|
||||
Method defineMethod = ((ClassType)classLoader.referenceType()).concreteMethodByName("defineClass", "(Ljava/lang/String;[BII)Ljava/lang/Class;");
|
||||
byte[] bytes = cls.toByteArray();
|
||||
ArrayList<Value> args = new ArrayList<Value>();
|
||||
args.add(proxy.mirrorOf(cls.myOrigName));
|
||||
args.add(mirrorOf(bytes, context, process));
|
||||
args.add(proxy.mirrorOf(0));
|
||||
args.add(proxy.mirrorOf(bytes.length));
|
||||
classLoader.invokeMethod(threadReference, defineMethod, args, ClassType.INVOKE_SINGLE_THREADED);
|
||||
}
|
||||
return (ClassType)process.findClass(context, GEN_CLASS_FULL_NAME, classLoader);
|
||||
}
|
||||
|
||||
private static ArrayReference mirrorOf(byte[] bytes, EvaluationContext context, DebugProcess process)
|
||||
throws EvaluateException, InvalidTypeException, ClassNotLoadedException {
|
||||
ArrayType arrayClass = (ArrayType)process.findClass(context, "byte[]", context.getClassLoader());
|
||||
ArrayReference reference = process.newInstance(arrayClass, bytes.length);
|
||||
reference.disableCollection();
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
reference.setValue(i, ((VirtualMachineProxyImpl)process.getVirtualMachineProxy()).mirrorOf(bytes[i]));
|
||||
}
|
||||
return reference;
|
||||
}
|
||||
|
||||
private static final String GEN_CLASS_NAME = "Evaluator";
|
||||
private static final String GEN_CLASS_PACKAGE = "dummy";
|
||||
private static final String GEN_CLASS_FULL_NAME = GEN_CLASS_PACKAGE + '.' + GEN_CLASS_NAME;
|
||||
private static final String GEN_METHOD_NAME = "eval";
|
||||
|
||||
private static String createClassCode(TextWithImports body) {
|
||||
StringBuilder text = new StringBuilder();
|
||||
text.append("package " + GEN_CLASS_PACKAGE + ";");
|
||||
String imports = body.getImports();
|
||||
if (!imports.isEmpty()) {
|
||||
for (String s : imports.split(",")) {
|
||||
text.append("import " + s + ";");
|
||||
}
|
||||
}
|
||||
String bodyText = body.getText();
|
||||
if (!bodyText.endsWith(";")) {
|
||||
bodyText += ';';
|
||||
}
|
||||
text.append("public class " + GEN_CLASS_NAME + " { public static Object " + GEN_METHOD_NAME + "() throws Exception {" + bodyText + "}}");
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
private static ArrayReference createURLArray(EvaluationContext context)
|
||||
throws EvaluateException, InvocationException, InvalidTypeException, ClassNotLoadedException, IncompatibleThreadStateException {
|
||||
DebugProcess process = context.getDebugProcess();
|
||||
ArrayType arrayType = (ArrayType)process.findClass(context, "java.net.URL[]", context.getClassLoader());
|
||||
ArrayReference arrayRef = arrayType.newInstance(1);
|
||||
ClassType classType = (ClassType)process.findClass(context, "java.net.URL", context.getClassLoader());
|
||||
VirtualMachineProxyImpl proxy = (VirtualMachineProxyImpl)process.getVirtualMachineProxy();
|
||||
ThreadReference threadReference = context.getSuspendContext().getThread().getThreadReference();
|
||||
ObjectReference reference = classType.newInstance(threadReference, classType.concreteMethodByName("<init>", "(Ljava/lang/String;)V"),
|
||||
Arrays.asList(proxy.mirrorOf("file:a")), ClassType.INVOKE_SINGLE_THREADED);
|
||||
arrayRef.setValues(Arrays.asList(reference));
|
||||
return arrayRef;
|
||||
}
|
||||
|
||||
///////////////// Compiler stuff
|
||||
|
||||
private Collection<OutputFileObject> compile() throws EvaluateException {
|
||||
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||
MemoryFileManager manager = new MemoryFileManager(compiler);
|
||||
DiagnosticCollector<JavaFileObject> diagnostic = new DiagnosticCollector<JavaFileObject>();
|
||||
if (!compiler.getTask(null, manager, diagnostic, null, null, Arrays
|
||||
.asList(new SourceFileObject(GEN_CLASS_NAME, JavaFileObject.Kind.SOURCE, createClassCode(myText)))).call()) {
|
||||
// TODO: show only errors
|
||||
throw new EvaluateException(diagnostic.getDiagnostics().get(0).getMessage(Locale.getDefault()));
|
||||
}
|
||||
return manager.classes;
|
||||
}
|
||||
|
||||
private static URI getUri(String name, JavaFileObject.Kind kind) {
|
||||
return URI.create("memo:///" + name.replace('.', '/') + kind.extension);
|
||||
}
|
||||
|
||||
private static class SourceFileObject extends SimpleJavaFileObject {
|
||||
private final String myContent;
|
||||
|
||||
SourceFileObject(String name, Kind kind, String content) {
|
||||
super(getUri(name, kind), kind);
|
||||
myContent = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getCharContent(boolean ignore) {
|
||||
return myContent;
|
||||
}
|
||||
}
|
||||
|
||||
private static class OutputFileObject extends SimpleJavaFileObject {
|
||||
private final ByteArrayOutputStream myStream = new ByteArrayOutputStream();
|
||||
private final String myOrigName;
|
||||
|
||||
OutputFileObject(String name, Kind kind) {
|
||||
super(getUri(name, kind), kind);
|
||||
myOrigName = name;
|
||||
}
|
||||
|
||||
byte[] toByteArray() {
|
||||
return myStream.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteArrayOutputStream openOutputStream() {
|
||||
return myStream;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MemoryFileManager extends ForwardingJavaFileManager {
|
||||
private final Collection<OutputFileObject> classes = new ArrayList<OutputFileObject>();
|
||||
|
||||
MemoryFileManager(JavaCompiler compiler) {
|
||||
super(compiler.getStandardFileManager(null, null, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputFileObject getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject source) {
|
||||
OutputFileObject mc = new OutputFileObject(name, kind);
|
||||
classes.add(mc);
|
||||
return mc;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+37
-29
@@ -21,21 +21,26 @@ import com.intellij.debugger.DebuggerInvocationUtil;
|
||||
import com.intellij.debugger.EvaluatingComputable;
|
||||
import com.intellij.debugger.engine.ContextUtil;
|
||||
import com.intellij.debugger.engine.StackFrameContext;
|
||||
import com.intellij.debugger.engine.evaluation.*;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
|
||||
import com.intellij.debugger.engine.evaluation.TextWithImports;
|
||||
import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator;
|
||||
import com.intellij.debugger.engine.evaluation.expression.Modifier;
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx;
|
||||
import com.intellij.debugger.impl.PositionUtil;
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl;
|
||||
import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.psi.PsiCodeFragment;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiExpressionCodeFragment;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.sun.jdi.ObjectCollectedException;
|
||||
import com.sun.jdi.ObjectReference;
|
||||
import com.sun.jdi.Value;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author lex
|
||||
@@ -43,7 +48,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
public abstract class EvaluationDescriptor extends ValueDescriptorImpl{
|
||||
private Modifier myModifier;
|
||||
protected TextWithImports myText;
|
||||
private CodeFragmentFactory myCodeFragmentFactory = null; // used to force specific context, e.g. from evaluation
|
||||
|
||||
protected EvaluationDescriptor(TextWithImports text, Project project, Value value) {
|
||||
super(project, value);
|
||||
@@ -56,38 +60,35 @@ public abstract class EvaluationDescriptor extends ValueDescriptorImpl{
|
||||
myText = text;
|
||||
}
|
||||
|
||||
public final void setCodeFragmentFactory(CodeFragmentFactory codeFragmentFactory) {
|
||||
myCodeFragmentFactory = codeFragmentFactory != null? new CodeFragmentFactoryContextWrapper(codeFragmentFactory) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public final CodeFragmentFactory getCodeFragmentFactory() {
|
||||
return myCodeFragmentFactory;
|
||||
}
|
||||
|
||||
protected final @NotNull CodeFragmentFactory getEffectiveCodeFragmentFactory(final PsiElement psiContext) {
|
||||
if (myCodeFragmentFactory != null) {
|
||||
return myCodeFragmentFactory;
|
||||
}
|
||||
return DebuggerUtilsEx.getEffectiveCodeFragmentFactory(psiContext);
|
||||
}
|
||||
|
||||
protected abstract EvaluationContextImpl getEvaluationContext (EvaluationContextImpl evaluationContext);
|
||||
|
||||
protected abstract PsiCodeFragment getEvaluationCode(StackFrameContext context) throws EvaluateException;
|
||||
|
||||
protected PsiCodeFragment createCodeFragment(PsiElement context) {
|
||||
TextWithImports text = getEvaluationText();
|
||||
final PsiCodeFragment fragment =
|
||||
DebuggerUtilsEx.findAppropriateCodeFragmentFactory(text, context).createCodeFragment(text, context, myProject);
|
||||
fragment.forceResolveScope(GlobalSearchScope.allScope(myProject));
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public final Value calcValue(final EvaluationContextImpl evaluationContext) throws EvaluateException {
|
||||
try {
|
||||
final EvaluationContextImpl thisEvaluationContext = getEvaluationContext(evaluationContext);
|
||||
|
||||
final ExpressionEvaluator evaluator = DebuggerInvocationUtil.commitAndRunReadAction(myProject, new EvaluatingComputable<ExpressionEvaluator>() {
|
||||
public ExpressionEvaluator compute() throws EvaluateException {
|
||||
final PsiElement psiContext = PositionUtil.getContextElement(evaluationContext);
|
||||
return getEffectiveCodeFragmentFactory(psiContext).getEvaluatorBuilder().build(getEvaluationCode(thisEvaluationContext),
|
||||
ContextUtil.getSourcePosition(thisEvaluationContext));
|
||||
}
|
||||
});
|
||||
|
||||
final ExpressionEvaluator evaluator;
|
||||
if (Registry.is("debugger.compiling.evaluator")) {
|
||||
evaluator = new CompilingEvaluator(getEvaluationText());
|
||||
}
|
||||
else {
|
||||
evaluator = DebuggerInvocationUtil.commitAndRunReadAction(myProject, new EvaluatingComputable<ExpressionEvaluator>() {
|
||||
public ExpressionEvaluator compute() throws EvaluateException {
|
||||
final PsiElement psiContext = PositionUtil.getContextElement(evaluationContext);
|
||||
return DebuggerUtilsEx.findAppropriateCodeFragmentFactory(getEvaluationText(), psiContext).getEvaluatorBuilder()
|
||||
.build(getEvaluationCode(thisEvaluationContext), ContextUtil.getSourcePosition(thisEvaluationContext));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!thisEvaluationContext.getDebugProcess().isAttached()) {
|
||||
throw EvaluateExceptionUtil.PROCESS_EXITED;
|
||||
@@ -99,7 +100,11 @@ public abstract class EvaluationDescriptor extends ValueDescriptorImpl{
|
||||
|
||||
final Value value = evaluator.evaluate(thisEvaluationContext);
|
||||
if (value instanceof ObjectReference) {
|
||||
thisEvaluationContext.getSuspendContext().keep(((ObjectReference)value));
|
||||
ObjectReference objRef = (ObjectReference)value;
|
||||
if (VirtualMachineProxyImpl.isCollected(objRef)) {
|
||||
throw EvaluateExceptionUtil.OBJECT_WAS_COLLECTED;
|
||||
}
|
||||
thisEvaluationContext.getSuspendContext().keep(objRef);
|
||||
}
|
||||
myModifier = evaluator.getModifier();
|
||||
setLvalue(myModifier != null);
|
||||
@@ -109,6 +114,9 @@ public abstract class EvaluationDescriptor extends ValueDescriptorImpl{
|
||||
catch (final EvaluateException ex) {
|
||||
throw new EvaluateException(ex.getLocalizedMessage(), ex);
|
||||
}
|
||||
catch (ObjectCollectedException ex) {
|
||||
throw EvaluateExceptionUtil.OBJECT_WAS_COLLECTED;
|
||||
}
|
||||
}
|
||||
|
||||
public String calcValueName() {
|
||||
|
||||
+14
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2009 JetBrains s.r.o.
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,6 +26,7 @@ import com.intellij.debugger.engine.evaluation.EvaluateException;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl;
|
||||
import com.intellij.debugger.impl.DebuggerContextUtil;
|
||||
import com.intellij.debugger.impl.DebuggerSession;
|
||||
import com.intellij.debugger.impl.PositionUtil;
|
||||
import com.intellij.debugger.settings.NodeRendererSettings;
|
||||
@@ -73,6 +74,12 @@ public class FieldDescriptorImpl extends ValueDescriptorImpl implements FieldDes
|
||||
@SuppressWarnings({"HardCodedStringLiteral"})
|
||||
@Nullable
|
||||
public SourcePosition getSourcePosition(final Project project, final DebuggerContextImpl context) {
|
||||
return getSourcePosition(project, context, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"HardCodedStringLiteral"})
|
||||
@Nullable
|
||||
public SourcePosition getSourcePosition(final Project project, final DebuggerContextImpl context, boolean nearest) {
|
||||
if (context.getFrameProxy() == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -95,6 +102,9 @@ public class FieldDescriptorImpl extends ValueDescriptorImpl implements FieldDes
|
||||
if (psiVariable == null) {
|
||||
return null;
|
||||
}
|
||||
if (nearest) {
|
||||
return DebuggerContextUtil.findNearest(context, psiVariable, aClass.getContainingFile());
|
||||
}
|
||||
return SourcePosition.createFromOffset(psiVariable.getContainingFile(), psiVariable.getTextOffset());
|
||||
}
|
||||
else {
|
||||
@@ -125,6 +135,9 @@ public class FieldDescriptorImpl extends ValueDescriptorImpl implements FieldDes
|
||||
aClass = (PsiClass) aClass.getNavigationElement();
|
||||
for (PsiField field : aClass.getFields()) {
|
||||
if (fieldName.equals(field.getName())) {
|
||||
if (nearest) {
|
||||
return DebuggerContextUtil.findNearest(context, field, aClass.getContainingFile());
|
||||
}
|
||||
return SourcePosition.createFromOffset(field.getContainingFile(), field.getTextOffset());
|
||||
}
|
||||
}
|
||||
|
||||
+10
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2009 JetBrains s.r.o.
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,7 @@ import com.intellij.debugger.engine.DebuggerUtils;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl;
|
||||
import com.intellij.debugger.impl.DebuggerContextUtil;
|
||||
import com.intellij.debugger.impl.PositionUtil;
|
||||
import com.intellij.debugger.jdi.LocalVariableProxyImpl;
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl;
|
||||
@@ -61,6 +62,11 @@ public class LocalVariableDescriptorImpl extends ValueDescriptorImpl implements
|
||||
|
||||
@Nullable
|
||||
public SourcePosition getSourcePosition(final Project project, final DebuggerContextImpl context) {
|
||||
return getSourcePosition(project, context, false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public SourcePosition getSourcePosition(final Project project, final DebuggerContextImpl context, boolean nearest) {
|
||||
StackFrameProxyImpl frame = context.getFrameProxy();
|
||||
if (frame == null) return null;
|
||||
|
||||
@@ -77,7 +83,9 @@ public class LocalVariableDescriptorImpl extends ValueDescriptorImpl implements
|
||||
|
||||
PsiFile containingFile = psiVariable.getContainingFile();
|
||||
if(containingFile == null) return null;
|
||||
|
||||
if (nearest) {
|
||||
return DebuggerContextUtil.findNearest(context, psiVariable, containingFile);
|
||||
}
|
||||
return SourcePosition.createFromOffset(containingFile, psiVariable.getTextOffset());
|
||||
}
|
||||
|
||||
|
||||
+3
-7
@@ -21,19 +21,18 @@
|
||||
package com.intellij.debugger.ui.impl.watch;
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle;
|
||||
import com.intellij.debugger.engine.DebuggerUtils;
|
||||
import com.intellij.debugger.engine.StackFrameContext;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
|
||||
import com.intellij.debugger.engine.evaluation.TextWithImports;
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx;
|
||||
import com.intellij.debugger.settings.NodeRendererSettings;
|
||||
import com.intellij.debugger.ui.tree.UserExpressionDescriptor;
|
||||
import com.intellij.debugger.ui.tree.render.ClassRenderer;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiCodeFragment;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.util.StringBuilderSpinAllocator;
|
||||
import com.sun.jdi.ObjectReference;
|
||||
import com.sun.jdi.Value;
|
||||
@@ -78,16 +77,13 @@ public class UserExpressionDescriptorImpl extends EvaluationDescriptor implement
|
||||
if(value instanceof ObjectReference) {
|
||||
final String typeName = value.type().name();
|
||||
|
||||
final PsiClass psiClass = DebuggerUtilsEx.findClass(myTypeName, myProject, context.getDebugProcess().getSearchScope());
|
||||
final PsiClass psiClass = DebuggerUtils.findClass(myTypeName, myProject, context.getDebugProcess().getSearchScope());
|
||||
|
||||
if (psiClass == null) {
|
||||
throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.invalid.type.name", typeName));
|
||||
}
|
||||
|
||||
final PsiCodeFragment fragment =
|
||||
getEffectiveCodeFragmentFactory(psiClass).createCodeFragment(getEvaluationText(), psiClass, myProject);
|
||||
fragment.forceResolveScope(GlobalSearchScope.allScope(myProject));
|
||||
return fragment;
|
||||
return createCodeFragment(psiClass);
|
||||
}
|
||||
else {
|
||||
throw EvaluateExceptionUtil.createEvaluateException(
|
||||
|
||||
@@ -203,6 +203,7 @@ public abstract class ValueDescriptorImpl extends NodeDescriptorImpl implements
|
||||
}
|
||||
catch (EvaluateException e) {
|
||||
myValueException = e;
|
||||
setFailed(e);
|
||||
myValue = getTargetExceptionWithStackTraceFilled(evaluationContext, e);
|
||||
myIsExpandable = false;
|
||||
}
|
||||
|
||||
@@ -28,8 +28,6 @@ import com.intellij.debugger.impl.PositionUtil;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.psi.PsiCodeFragment;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.sun.jdi.Value;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -85,9 +83,6 @@ public class WatchItemDescriptor extends EvaluationDescriptor {
|
||||
}
|
||||
|
||||
protected PsiCodeFragment getEvaluationCode(StackFrameContext context) throws EvaluateException {
|
||||
final PsiElement psiContext = PositionUtil.getContextElement(context);
|
||||
final PsiCodeFragment fragment = getEffectiveCodeFragmentFactory(psiContext).createCodeFragment(getEvaluationText(), psiContext, myProject);
|
||||
fragment.forceResolveScope(GlobalSearchScope.allScope(myProject));
|
||||
return fragment;
|
||||
return createCodeFragment(PositionUtil.getContextElement(context));
|
||||
}
|
||||
}
|
||||
+7
-10
@@ -1,11 +1,9 @@
|
||||
package org.jetbrains.java.debugger;
|
||||
|
||||
import com.intellij.debugger.engine.evaluation.CodeFragmentFactory;
|
||||
import com.intellij.debugger.engine.evaluation.CodeFragmentFactoryContextWrapper;
|
||||
import com.intellij.debugger.engine.evaluation.TextWithImports;
|
||||
import com.intellij.debugger.engine.evaluation.TextWithImportsImpl;
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx;
|
||||
import com.intellij.debugger.ui.DebuggerEditorImpl;
|
||||
import com.intellij.ide.highlighter.JavaFileType;
|
||||
import com.intellij.lang.Language;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
@@ -73,16 +71,15 @@ public class JavaDebuggerEditorsProvider extends XDebuggerEditorsProviderBase {
|
||||
boolean isPhysical) {
|
||||
TextWithImports text = TextWithImportsImpl.fromXExpression(expression);
|
||||
if (text != null && context != null) {
|
||||
CodeFragmentFactory factory = new CodeFragmentFactoryContextWrapper(DebuggerEditorImpl.findAppropriateFactory(text, context));
|
||||
CodeFragmentFactory factory = DebuggerUtilsEx.findAppropriateCodeFragmentFactory(text, context);
|
||||
JavaCodeFragment codeFragment = factory.createPresentationCodeFragment(text, context, project);
|
||||
codeFragment.forceResolveScope(GlobalSearchScope.allScope(project));
|
||||
if (context != null) {
|
||||
final PsiClass contextClass = PsiTreeUtil.getNonStrictParentOfType(context, PsiClass.class);
|
||||
if (contextClass != null) {
|
||||
final PsiClassType contextType =
|
||||
JavaPsiFacade.getInstance(codeFragment.getProject()).getElementFactory().createType(contextClass);
|
||||
codeFragment.setThisType(contextType);
|
||||
}
|
||||
|
||||
final PsiClass contextClass = PsiTreeUtil.getNonStrictParentOfType(context, PsiClass.class);
|
||||
if (contextClass != null) {
|
||||
final PsiClassType contextType =
|
||||
JavaPsiFacade.getInstance(codeFragment.getProject()).getElementFactory().createType(contextClass);
|
||||
codeFragment.setThisType(contextType);
|
||||
}
|
||||
return codeFragment;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2009 JetBrains s.r.o.
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -128,7 +128,12 @@ public abstract class SourcePosition implements Navigatable{
|
||||
return true;
|
||||
}
|
||||
final PsiElement psiElement = myPsiElement;
|
||||
return psiElement != null && !psiElement.isValid();
|
||||
return psiElement != null && !ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
|
||||
@Override
|
||||
public Boolean compute() {
|
||||
return psiElement.isValid();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -194,7 +199,7 @@ public abstract class SourcePosition implements Navigatable{
|
||||
|
||||
@Nullable
|
||||
protected PsiElement calcPsiElement() {
|
||||
PsiFile psiFile = getFile();
|
||||
final PsiFile psiFile = getFile();
|
||||
int lineNumber = getLine();
|
||||
if(lineNumber < 0) {
|
||||
return psiFile;
|
||||
@@ -207,47 +212,54 @@ public abstract class SourcePosition implements Navigatable{
|
||||
if (lineNumber >= document.getLineCount()) {
|
||||
return psiFile;
|
||||
}
|
||||
int startOffset = document.getLineStartOffset(lineNumber);
|
||||
final int startOffset = document.getLineStartOffset(lineNumber);
|
||||
if(startOffset == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PsiElement rootElement = psiFile;
|
||||
return ApplicationManager.getApplication().runReadAction(new Computable<PsiElement>() {
|
||||
@Override
|
||||
public PsiElement compute() {
|
||||
PsiElement rootElement = psiFile;
|
||||
|
||||
List<PsiFile> allFiles = psiFile.getViewProvider().getAllFiles();
|
||||
if (allFiles.size() > 1) { // jsp & gsp
|
||||
PsiClassOwner owner = ContainerUtil.findInstance(allFiles, PsiClassOwner.class);
|
||||
if (owner != null) {
|
||||
PsiClass[] classes = owner.getClasses();
|
||||
if (classes.length == 1 && classes[0] instanceof SyntheticElement) {
|
||||
rootElement = classes[0];
|
||||
List<PsiFile> allFiles = psiFile.getViewProvider().getAllFiles();
|
||||
if (allFiles.size() > 1) { // jsp & gsp
|
||||
PsiClassOwner owner = ContainerUtil.findInstance(allFiles, PsiClassOwner.class);
|
||||
if (owner != null) {
|
||||
PsiClass[] classes = owner.getClasses();
|
||||
if (classes.length == 1 && classes[0] instanceof SyntheticElement) {
|
||||
rootElement = classes[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PsiElement element;
|
||||
while(true) {
|
||||
final CharSequence charsSequence = document.getCharsSequence();
|
||||
for (; startOffset < charsSequence.length(); startOffset++) {
|
||||
char c = charsSequence.charAt(startOffset);
|
||||
if (c != ' ' && c != '\t') {
|
||||
break;
|
||||
PsiElement element = null;
|
||||
int offset = startOffset;
|
||||
while (true) {
|
||||
final CharSequence charsSequence = document.getCharsSequence();
|
||||
for (; offset < charsSequence.length(); offset++) {
|
||||
char c = charsSequence.charAt(offset);
|
||||
if (c != ' ' && c != '\t') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (offset >= charsSequence.length()) break;
|
||||
|
||||
element = rootElement.findElementAt(offset);
|
||||
|
||||
if (element instanceof PsiComment) {
|
||||
offset = element.getTextRange().getEndOffset() + 1;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (element != null && element.getParent() instanceof PsiForStatement) {
|
||||
return ((PsiForStatement)element.getParent()).getInitialization();
|
||||
}
|
||||
return element;
|
||||
}
|
||||
element = rootElement.findElementAt(startOffset);
|
||||
|
||||
if(element instanceof PsiComment) {
|
||||
startOffset = element.getTextRange().getEndOffset() + 1;
|
||||
}
|
||||
else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (element != null && element.getParent() instanceof PsiForStatement) {
|
||||
return ((PsiForStatement)element.getParent()).getInitialization();
|
||||
}
|
||||
return element;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,10 +69,7 @@ public class DefaultJavaProgramRunner extends JavaPatchableProgramRunner {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RunContentDescriptor doExecute(@NotNull final Project project,
|
||||
@NotNull final RunProfileState state,
|
||||
final RunContentDescriptor contentToReuse,
|
||||
@NotNull final ExecutionEnvironment env) throws ExecutionException {
|
||||
protected RunContentDescriptor doExecute(@NotNull final RunProfileState state, @NotNull final ExecutionEnvironment env) throws ExecutionException {
|
||||
FileDocumentManager.getInstance().saveAllDocuments();
|
||||
|
||||
ExecutionResult executionResult;
|
||||
@@ -100,11 +97,11 @@ public class DefaultJavaProgramRunner extends JavaPatchableProgramRunner {
|
||||
onProcessStarted(env.getRunnerSettings(), executionResult);
|
||||
|
||||
final RunContentBuilder contentBuilder = new RunContentBuilder(executionResult, env);
|
||||
Disposer.register(project, contentBuilder);
|
||||
Disposer.register(env.getProject(), contentBuilder);
|
||||
if (shouldAddDefaultActions) {
|
||||
addDefaultActions(contentBuilder);
|
||||
}
|
||||
return contentBuilder.showRunContent(contentToReuse);
|
||||
return contentBuilder.showRunContent(env.getContentToReuse());
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2012 JetBrains s.r.o.
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,6 +27,7 @@ import com.intellij.openapi.fileEditor.TextEditor;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.openapi.util.Conditions;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.searches.ClassInheritorsSearch;
|
||||
import com.intellij.psi.util.PsiClassUtil;
|
||||
@@ -59,7 +60,7 @@ public class InheritorChooser {
|
||||
final Runnable performRunnable,
|
||||
final PsiMethod psiMethod,
|
||||
final PsiClass containingClass) {
|
||||
return runMethodInAbstractClass(context, performRunnable, psiMethod, containingClass, Condition.TRUE);
|
||||
return runMethodInAbstractClass(context, performRunnable, psiMethod, containingClass, Conditions.<PsiClass>alwaysTrue());
|
||||
}
|
||||
|
||||
public boolean runMethodInAbstractClass(final ConfigurationContext context,
|
||||
|
||||
@@ -246,7 +246,7 @@ public class LibraryOptionsPanel implements Disposable {
|
||||
onVersionChanged(getPresentableVersion());
|
||||
}
|
||||
});
|
||||
myExistingLibraryComboBox.setRenderer(new ColoredListCellRenderer() {
|
||||
myExistingLibraryComboBox.setRenderer(new ColoredListCellRenderer(myExistingLibraryComboBox) {
|
||||
@Override
|
||||
protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) {
|
||||
if (value == null) {
|
||||
|
||||
@@ -16,10 +16,7 @@
|
||||
package com.intellij.ide;
|
||||
|
||||
import com.intellij.ide.impl.ProjectUtil;
|
||||
import com.intellij.openapi.components.RoamingType;
|
||||
import com.intellij.openapi.components.State;
|
||||
import com.intellij.openapi.components.Storage;
|
||||
import com.intellij.openapi.components.StoragePathMacros;
|
||||
import com.intellij.openapi.components.*;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.util.messages.MessageBus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -28,19 +25,22 @@ import org.jetbrains.annotations.NotNull;
|
||||
name = "RecentProjectsManager",
|
||||
roamingType = RoamingType.DISABLED,
|
||||
storages = {
|
||||
@Storage(
|
||||
file = StoragePathMacros.APP_CONFIG + "/other.xml"
|
||||
)}
|
||||
@Storage(file = StoragePathMacros.APP_CONFIG + "/other.xml"),
|
||||
@Storage(file = StoragePathMacros.APP_CONFIG + "/recentProjects.xml")
|
||||
},
|
||||
storageChooser = LastStorageChooserForWrite.class
|
||||
)
|
||||
public class RecentProjectsManager extends RecentProjectsManagerBase {
|
||||
public RecentProjectsManager(MessageBus messageBus) {
|
||||
super(messageBus);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProjectPath(@NotNull Project project) {
|
||||
return project.getPresentableUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doOpenProject(@NotNull String projectPath, Project projectToClose, boolean forceOpenInNewFrame) {
|
||||
ProjectUtil.openProject(projectPath, projectToClose, forceOpenInNewFrame);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2012 JetBrains s.r.o.
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,6 +24,7 @@ import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.Presentation;
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
|
||||
import com.intellij.openapi.fileChooser.FileChooserDialog;
|
||||
import com.intellij.openapi.fileChooser.FileChooserFactory;
|
||||
import com.intellij.openapi.module.Module;
|
||||
@@ -39,7 +40,6 @@ import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -94,14 +94,7 @@ public class ImportModuleAction extends AnAction {
|
||||
|
||||
@Nullable
|
||||
public static AddModuleWizard selectFileAndCreateWizard(final Project project, Component dialogParent) {
|
||||
FileChooserDescriptor descriptor = new FileChooserDescriptor(true, true, true, true, false, false) {
|
||||
FileChooserDescriptor myDelegate = new OpenProjectFileChooserDescriptor(true);
|
||||
@Override
|
||||
public Icon getIcon(VirtualFile file) {
|
||||
Icon icon = myDelegate.getIcon(file);
|
||||
return icon == null ? super.getIcon(file) : icon;
|
||||
}
|
||||
};
|
||||
FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleLocalFileDescriptor();
|
||||
descriptor.setHideIgnored(false);
|
||||
descriptor.setTitle("Select File or Directory to Import");
|
||||
ProjectImportProvider[] providers = ProjectImportProvider.PROJECT_IMPORT_PROVIDER.getExtensions();
|
||||
@@ -123,7 +116,7 @@ public class ImportModuleAction extends AnAction {
|
||||
if (lastLocation != null) {
|
||||
toSelect = LocalFileSystem.getInstance().refreshAndFindFileByPath(lastLocation);
|
||||
}
|
||||
VirtualFile[] files = chooser.choose(toSelect, project);
|
||||
VirtualFile[] files = chooser.choose(project, toSelect);
|
||||
if (files.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2013 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.ide.actions;
|
||||
|
||||
import com.intellij.ide.IdeBundle;
|
||||
import com.intellij.ide.highlighter.ProjectFileType;
|
||||
import com.intellij.ide.impl.ProjectUtil;
|
||||
import com.intellij.openapi.actionSystem.ActionPlaces;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
import com.intellij.openapi.actionSystem.PlatformDataKeys;
|
||||
import com.intellij.openapi.extensions.Extensions;
|
||||
import com.intellij.openapi.fileChooser.FileChooser;
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
|
||||
import com.intellij.openapi.fileChooser.PathChooserDialog;
|
||||
import com.intellij.openapi.project.DumbAware;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.projectImport.ProjectOpenProcessor;
|
||||
import com.intellij.projectImport.ProjectOpenProcessorBase;
|
||||
import com.intellij.util.Consumer;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class OpenProjectAction extends AnAction implements DumbAware {
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
final FileChooserDescriptor descriptor = new OpenProjectFileChooserDescriptor(true);
|
||||
descriptor.setTitle(IdeBundle.message("title.open.project"));
|
||||
final Set<String> extensions = new LinkedHashSet<String>();
|
||||
extensions.add(ProjectFileType.DOT_DEFAULT_EXTENSION);
|
||||
final ProjectOpenProcessor[] openProcessors = Extensions.getExtensions(ProjectOpenProcessor.EXTENSION_POINT_NAME);
|
||||
for (ProjectOpenProcessor openProcessor : openProcessors) {
|
||||
if (!(openProcessor instanceof ProjectOpenProcessorBase)) continue;
|
||||
final String[] supportedExtensions = ((ProjectOpenProcessorBase)openProcessor).getSupportedExtensions();
|
||||
if (supportedExtensions != null) {
|
||||
Collections.addAll(extensions, supportedExtensions);
|
||||
}
|
||||
}
|
||||
descriptor.setDescription(IdeBundle.message("filter.project.files", StringUtil.join(extensions, ", ")));
|
||||
|
||||
VirtualFile userHomeDir = null;
|
||||
if (SystemInfo.isUnix) {
|
||||
userHomeDir = VfsUtil.getUserHomeDir();
|
||||
}
|
||||
|
||||
descriptor.putUserData(PathChooserDialog.PREFER_LAST_OVER_EXPLICIT, Boolean.TRUE);
|
||||
|
||||
final Project project = CommonDataKeys.PROJECT.getData(e.getDataContext());
|
||||
FileChooser.chooseFiles(descriptor, project, userHomeDir, new Consumer<List<VirtualFile>>() {
|
||||
@Override
|
||||
public void consume(final List<VirtualFile> files) {
|
||||
if (files.size() == 1) {
|
||||
ProjectUtil.openOrImport(files.get(0).getPath(), project, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(AnActionEvent e) {
|
||||
super.update(e);
|
||||
e.getPresentation().setEnabledAndVisible(ActionPlaces.WELCOME_SCREEN.equals(e.getPlace()));
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ import javax.swing.border.CompoundBorder;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
||||
public class ShowStructureSettingsAction extends AnAction implements DumbAware {
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
Project project = CommonDataKeys.PROJECT.getData(e.getDataContext());
|
||||
if (project == null) {
|
||||
@@ -56,12 +57,13 @@ public class ShowStructureSettingsAction extends AnAction implements DumbAware {
|
||||
protected JComponent createSouthPanel() {
|
||||
JComponent panel = super.createSouthPanel();
|
||||
assert panel != null;
|
||||
CustomLineBorder line = new CustomLineBorder(new JBColor(Gray._153, Gray._80), 1, 0, 0, 0);
|
||||
CustomLineBorder line = new CustomLineBorder(new JBColor(Gray._153.withAlpha(128), Gray._100.withAlpha(128)), 1, 0, 0, 0);
|
||||
panel.setBorder(new CompoundBorder(line, new EmptyBorder(10, 5, 5, 5)));
|
||||
return panel;
|
||||
}
|
||||
}.show();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
ShowSettingsUtil
|
||||
.getInstance().editConfigurable(project, OptionsEditorDialog.DIMENSION_KEY, ProjectStructureConfigurable.getInstance(project));
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.intellij.openapi.project.ex.ProjectManagerEx;
|
||||
import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable;
|
||||
|
||||
public class TemplateProjectStructureAction extends AnAction implements DumbAware {
|
||||
@Override
|
||||
public void actionPerformed(final AnActionEvent e) {
|
||||
Project defaultProject = ProjectManagerEx.getInstanceEx().getDefaultProject();
|
||||
ShowSettingsUtil.getInstance().editConfigurable(defaultProject, OptionsEditorDialog.DIMENSION_KEY, ProjectStructureConfigurable.getInstance(defaultProject));
|
||||
|
||||
@@ -206,7 +206,9 @@ public abstract class ModuleEditor implements Place.Navigator, Disposable {
|
||||
myEditors.add(new ModuleConfigurableWrapper(moduleConfigurable));
|
||||
}
|
||||
for(ModuleConfigurableEP extension : myModule.getExtensions(MODULE_CONFIGURABLES)) {
|
||||
myEditors.add(new ModuleConfigurableWrapper(extension.createConfigurable()));
|
||||
if (extension.isConfigurableProvided()) {
|
||||
myEditors.add(new ModuleConfigurableWrapper(extension.createConfigurable()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -130,7 +130,8 @@ public class DefaultLibraryRootsComponentDescriptor extends LibraryRootsComponen
|
||||
public boolean isAccepted(@NotNull VirtualFile rootCandidate, @NotNull ProgressIndicator progressIndicator) {
|
||||
if (rootCandidate.isDirectory()) {
|
||||
for (VirtualFile file : rootCandidate.getChildren()) {
|
||||
if (NATIVE_LIBRARY_EXTENSIONS.contains(file.getExtension())) {
|
||||
String extension = file.getExtension();
|
||||
if (extension != null && NATIVE_LIBRARY_EXTENSIONS.contains(extension)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -29,10 +29,9 @@ import com.intellij.openapi.roots.ui.OrderRootTypeUIFactory;
|
||||
import javax.swing.*;
|
||||
|
||||
public class NativeLibraryOrderRootTypeUIFactory implements OrderRootTypeUIFactory {
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return AllIcons.Modules.Library;
|
||||
return AllIcons.Nodes.NativeLibrariesFolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
@@ -76,6 +76,9 @@ public class LanguageLevelModuleExtension extends ModuleExtension<LanguageLevelM
|
||||
//bad value was stored
|
||||
}
|
||||
}
|
||||
else {
|
||||
myLanguageLevel = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+10
@@ -20,6 +20,7 @@ import com.intellij.codeInspection.dataFlow.ContractInference;
|
||||
import com.intellij.codeInspection.dataFlow.MethodContract;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiAnnotation;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiModifierListOwner;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
@@ -32,9 +33,17 @@ import java.util.List;
|
||||
import static com.intellij.codeInspection.dataFlow.ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT;
|
||||
|
||||
public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager {
|
||||
|
||||
@NotNull
|
||||
private static PsiModifierListOwner preferCompiledElement(@NotNull PsiModifierListOwner element) {
|
||||
PsiElement original = element.getOriginalElement();
|
||||
return original instanceof PsiModifierListOwner ? (PsiModifierListOwner)original : element;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PsiAnnotation findInferredAnnotation(@NotNull PsiModifierListOwner listOwner, @NotNull String annotationFQN) {
|
||||
listOwner = preferCompiledElement(listOwner);
|
||||
PsiAnnotation fromBytecode = ProjectBytecodeAnalysis.getInstance(listOwner.getProject()).findInferredAnnotation(listOwner, annotationFQN);
|
||||
if (fromBytecode != null) {
|
||||
return fromBytecode;
|
||||
@@ -57,6 +66,7 @@ public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager {
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiAnnotation[] findInferredAnnotations(@NotNull PsiModifierListOwner listOwner) {
|
||||
listOwner = preferCompiledElement(listOwner);
|
||||
List<PsiAnnotation> result = ContainerUtil.newArrayList();
|
||||
PsiAnnotation[] fromBytecode = ProjectBytecodeAnalysis.getInstance(listOwner.getProject()).findInferredAnnotations(listOwner);
|
||||
for (PsiAnnotation annotation : fromBytecode) {
|
||||
|
||||
+3
@@ -67,6 +67,9 @@ public class RedundantLambdaCodeBlockInspection extends BaseJavaBatchLocalInspec
|
||||
if (body instanceof PsiCodeBlock) {
|
||||
PsiExpression psiExpression = getExpression((PsiCodeBlock)body);
|
||||
if (psiExpression != null) {
|
||||
if (!expression.isVoidCompatible() && LambdaUtil.isExpressionStatementExpression(psiExpression)) {
|
||||
return;
|
||||
}
|
||||
final PsiElement errorElement;
|
||||
final PsiElement parent = psiExpression.getParent();
|
||||
if (parent instanceof PsiReturnStatement) {
|
||||
|
||||
+12
-4
@@ -28,6 +28,8 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.Frame;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static com.intellij.codeInspection.bytecodeAnalysis.Direction.*;
|
||||
|
||||
class AbstractValues {
|
||||
static final class ParamValue extends BasicValue {
|
||||
ParamValue(Type tp) {
|
||||
@@ -200,6 +202,14 @@ abstract class Analysis<Res> {
|
||||
|
||||
public static final int STEPS_LIMIT = 30000;
|
||||
public static final int EQUATION_SIZE_LIMIT = 30;
|
||||
|
||||
protected static final ThreadLocal<State[]> ourPendingStates = new ThreadLocal<State[]>() {
|
||||
@Override
|
||||
protected State[] initialValue() {
|
||||
return new State[STEPS_LIMIT];
|
||||
}
|
||||
};
|
||||
|
||||
final RichControlFlow richControlFlow;
|
||||
final Direction direction;
|
||||
final ControlFlowGraph controlFlow;
|
||||
@@ -266,10 +276,8 @@ abstract class Analysis<Res> {
|
||||
}
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
BasicValue value;
|
||||
if (direction instanceof InOut && ((InOut)direction).paramIndex == i) {
|
||||
value = new AbstractValues.ParamValue(args[i]);
|
||||
}
|
||||
else if (direction instanceof In && ((In)direction).paramIndex == i) {
|
||||
if (direction instanceof InOut && ((InOut)direction).paramIndex == i ||
|
||||
direction instanceof In && ((In)direction).paramIndex == i) {
|
||||
value = new AbstractValues.ParamValue(args[i]);
|
||||
}
|
||||
else {
|
||||
|
||||
+27
-36
@@ -28,6 +28,7 @@ import java.security.NoSuchAlgorithmException;
|
||||
import java.util.*;
|
||||
|
||||
import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalysis.LOG;
|
||||
import static com.intellij.codeInspection.bytecodeAnalysis.Direction.*;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
@@ -272,31 +273,42 @@ public class BytecodeAnalysisConverter {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int mkDirectionKey(Direction dir) {
|
||||
if (dir instanceof Out) {
|
||||
|
||||
static int mkDirectionKey(Direction dir) {
|
||||
if (dir == Out) {
|
||||
return 0;
|
||||
} else if (dir instanceof In) {
|
||||
}
|
||||
else if (dir == NullableOut) {
|
||||
return 1;
|
||||
}
|
||||
else if (dir instanceof In) {
|
||||
In in = (In)dir;
|
||||
return 8 * in.paramId() + 1;
|
||||
} else {
|
||||
// nullity mask is 0/1
|
||||
return 2 + 8 * in.paramId() + in.nullityMask;
|
||||
}
|
||||
else {
|
||||
InOut inOut = (InOut)dir;
|
||||
return 8 * inOut.paramId() + 2 + inOut.valueId();
|
||||
return 4 + 8 * inOut.paramId() + inOut.valueId();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Direction extractDirection(int directionKey) {
|
||||
if (directionKey == 0) {
|
||||
return new Out();
|
||||
return Out;
|
||||
}
|
||||
else if (directionKey == 1) {
|
||||
return NullableOut;
|
||||
}
|
||||
else {
|
||||
directionKey--;
|
||||
int paramId = directionKey / 8;
|
||||
int subDirection = directionKey % 8;
|
||||
if (subDirection == 1) {
|
||||
return new In(paramId);
|
||||
if (subDirection <= 2) {
|
||||
return new In(paramId, subDirection - 1);
|
||||
}
|
||||
else {
|
||||
return new InOut(paramId, Value.values()[subDirection - 2]);
|
||||
return new InOut(paramId, Value.values()[subDirection - 3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -328,14 +340,14 @@ public class BytecodeAnalysisConverter {
|
||||
* Given `solution` of all dependencies of a method with the `methodKey`, converts this solution into annotations.
|
||||
*
|
||||
* @param solution solution of equations
|
||||
* @param annotations annotations to which corresponding solutions should be added
|
||||
* @param methodAnnotations annotations to which corresponding solutions should be added
|
||||
* @param methodKey a primary key of a method being analyzed
|
||||
* @param arity arity of this method (hint for constructing @Contract annotations)
|
||||
*/
|
||||
public static void addMethodAnnotations(@NotNull HashMap<HKey, Value> solution, @NotNull Annotations annotations, @NotNull HKey methodKey, int arity) {
|
||||
public static void addMethodAnnotations(@NotNull HashMap<HKey, Value> solution, @NotNull MethodAnnotations methodAnnotations, @NotNull HKey methodKey, int arity) {
|
||||
List<String> clauses = new ArrayList<String>();
|
||||
HashSet<HKey> notNulls = annotations.notNulls;
|
||||
HashMap<HKey, String> contracts = annotations.contracts;
|
||||
HashSet<HKey> notNulls = methodAnnotations.notNulls;
|
||||
HashMap<HKey, String> contracts = methodAnnotations.contracts;
|
||||
for (Map.Entry<HKey, Value> entry : solution.entrySet()) {
|
||||
HKey key = entry.getKey().mkStable();
|
||||
Value value = entry.getValue();
|
||||
@@ -343,7 +355,7 @@ public class BytecodeAnalysisConverter {
|
||||
continue;
|
||||
}
|
||||
Direction direction = extractDirection(key.dirKey);
|
||||
if (value == Value.NotNull && direction instanceof Out && methodKey.equals(key)) {
|
||||
if (value == Value.NotNull && direction == Out && methodKey.equals(key)) {
|
||||
notNulls.add(key);
|
||||
}
|
||||
else if (direction instanceof InOut) {
|
||||
@@ -363,27 +375,6 @@ public class BytecodeAnalysisConverter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts solutions for equations over parameters into annotations.
|
||||
*
|
||||
* @param solution
|
||||
* @param annotations
|
||||
*/
|
||||
public static void addParameterAnnotations(@NotNull HashMap<HKey, Value> solution, @NotNull Annotations annotations) {
|
||||
HashSet<HKey> notNulls = annotations.notNulls;
|
||||
for (Map.Entry<HKey, Value> entry : solution.entrySet()) {
|
||||
HKey key = entry.getKey().mkStable();
|
||||
Value value = entry.getValue();
|
||||
if (value == Value.Top || value == Value.Bot) {
|
||||
continue;
|
||||
}
|
||||
Direction direction = extractDirection(key.dirKey);
|
||||
if (value == Value.NotNull && (direction instanceof In || direction instanceof Out)) {
|
||||
notNulls.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String contractValueString(@NotNull Value v) {
|
||||
switch (v) {
|
||||
case False: return "false";
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ public class BytecodeAnalysisIndex extends FileBasedIndexExtension<Bytes, HEquat
|
||||
private static final ClassDataIndexer INDEXER = new ClassDataIndexer();
|
||||
private static final HKeyDescriptor KEY_DESCRIPTOR = new HKeyDescriptor();
|
||||
|
||||
private static final int ourInternalVersion = 0;
|
||||
private static final int ourInternalVersion = 2;
|
||||
private static boolean ourEnabled = SystemProperties.getBooleanProperty("idea.enable.bytecode.contract.inference", true);
|
||||
|
||||
@NotNull
|
||||
|
||||
+74
-87
@@ -18,8 +18,6 @@ package com.intellij.codeInspection.bytecodeAnalysis;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.asm.*;
|
||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.util.NotNullLazyValue;
|
||||
import com.intellij.openapi.util.NullableLazyValue;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.util.indexing.DataIndexer;
|
||||
import com.intellij.util.indexing.FileContent;
|
||||
@@ -31,6 +29,7 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.*;
|
||||
|
||||
import static com.intellij.codeInspection.bytecodeAnalysis.Direction.*;
|
||||
import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalysis.LOG;
|
||||
|
||||
/**
|
||||
@@ -42,6 +41,7 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
public static final Final<Key, Value> FINAL_TOP = new Final<Key, Value>(Value.Top);
|
||||
public static final Final<Key, Value> FINAL_BOT = new Final<Key, Value>(Value.Bot);
|
||||
public static final Final<Key, Value> FINAL_NOT_NULL = new Final<Key, Value>(Value.NotNull);
|
||||
public static final Final<Key, Value> FINAL_NULL = new Final<Key, Value>(Value.Null);
|
||||
private static final List<Equation<Key, Value>> EMPTY_EQUATIONS = Collections.EMPTY_LIST;
|
||||
|
||||
@NotNull
|
||||
@@ -124,27 +124,26 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
final Method method = new Method(className, methodNode.name, methodNode.desc);
|
||||
final boolean stable = stableClass || (methodNode.access & STABLE_FLAGS) != 0 || "<init>".equals(methodNode.name);
|
||||
|
||||
Key primaryKey = new Key(method, new Out(), stable);
|
||||
Key primaryKey = new Key(method, Out, stable);
|
||||
if (argumentTypes.length == 0 && !isInterestingResult) {
|
||||
return Pair.create(primaryKey, EMPTY_EQUATIONS);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
final ControlFlowGraph graph = ControlFlowGraph.build(className, methodNode, jsr);
|
||||
if (graph.transitions.length > 0) {
|
||||
final DFSTree dfs = DFSTree.build(graph.transitions, graph.edgeCount);
|
||||
boolean complex = !dfs.back.isEmpty();
|
||||
if (!complex) {
|
||||
boolean branching = !dfs.back.isEmpty();
|
||||
if (!branching) {
|
||||
for (int[] transition : graph.transitions) {
|
||||
if (transition != null && transition.length > 1) {
|
||||
complex = true;
|
||||
branching = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (complex) {
|
||||
if (branching) {
|
||||
RichControlFlow richControlFlow = new RichControlFlow(graph, dfs);
|
||||
if (richControlFlow.reducible()) {
|
||||
return Pair.create(primaryKey,
|
||||
@@ -172,15 +171,15 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
}
|
||||
|
||||
private List<Equation<Key, Value>> processBranchingMethod(final Method method,
|
||||
final MethodNode methodNode,
|
||||
final RichControlFlow richControlFlow,
|
||||
Type[] argumentTypes,
|
||||
boolean isReferenceResult,
|
||||
boolean isInterestingResult,
|
||||
final boolean stable,
|
||||
boolean jsr) throws AnalyzerException {
|
||||
final MethodNode methodNode,
|
||||
final RichControlFlow richControlFlow,
|
||||
Type[] argumentTypes,
|
||||
boolean isReferenceResult,
|
||||
boolean isInterestingResult,
|
||||
final boolean stable,
|
||||
boolean jsr) throws AnalyzerException {
|
||||
|
||||
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 3 + 1);
|
||||
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 4 + 2);
|
||||
boolean maybeLeakingParameter = isInterestingResult;
|
||||
for (Type argType : argumentTypes) {
|
||||
if (ASMUtils.isReferenceType(argType) || (isReferenceResult && ASMUtils.isBooleanType(argType))) {
|
||||
@@ -191,39 +190,25 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
|
||||
final LeakingParameters leakingParametersAndFrames =
|
||||
maybeLeakingParameter ? leakingParametersAndFrames(method, methodNode, argumentTypes, jsr) : null;
|
||||
|
||||
boolean[] leakingParameters =
|
||||
leakingParametersAndFrames != null ? leakingParametersAndFrames.parameters : null;
|
||||
boolean[] leakingNullableParameters =
|
||||
leakingParametersAndFrames != null ? leakingParametersAndFrames.nullableParameters : null;
|
||||
|
||||
final NullableLazyValue<boolean[]> origins = new NullableLazyValue<boolean[]>() {
|
||||
@Override
|
||||
protected boolean[] compute() {
|
||||
try {
|
||||
return OriginsAnalysis.resultOrigins(leakingParametersAndFrames.frames, methodNode.instructions, richControlFlow.controlFlow);
|
||||
}
|
||||
catch (AnalyzerException e) {
|
||||
LOG.debug("when processing " + method + " in " + presentableUrl, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
final boolean[] origins =
|
||||
isInterestingResult ?
|
||||
OriginsAnalysis.resultOrigins(leakingParametersAndFrames.frames, methodNode.instructions, richControlFlow.controlFlow) :
|
||||
null;
|
||||
|
||||
NotNullLazyValue<Equation<Key, Value>> outEquation = new NotNullLazyValue<Equation<Key, Value>>() {
|
||||
@NotNull
|
||||
@Override
|
||||
protected Equation<Key, Value> compute() {
|
||||
if (origins.getValue() != null) {
|
||||
try {
|
||||
return new InOutAnalysis(richControlFlow, new Out(), origins.getValue(), stable).analyze();
|
||||
}
|
||||
catch (AnalyzerException ignored) {
|
||||
}
|
||||
}
|
||||
return new Equation<Key, Value>(new Key(method, new Out(), stable), FINAL_TOP);
|
||||
}
|
||||
};
|
||||
Equation<Key, Value> outEquation =
|
||||
isInterestingResult ?
|
||||
new InOutAnalysis(richControlFlow, Out, origins, stable).analyze() :
|
||||
null;
|
||||
|
||||
if (isReferenceResult) {
|
||||
result.add(outEquation.getValue());
|
||||
result.add(outEquation);
|
||||
result.add(new Equation<Key, Value>(new Key(method, NullableOut, stable), NullableMethodAnalysis.analyze(methodNode, origins, jsr)));
|
||||
}
|
||||
|
||||
for (int i = 0; i < argumentTypes.length; i++) {
|
||||
@@ -231,59 +216,57 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
boolean notNullParam = false;
|
||||
|
||||
if (isReferenceArg) {
|
||||
boolean possibleNPE = false;
|
||||
if (leakingParameters[i]) {
|
||||
Equation<Key, Value> notNullParamEquation = new NonNullInAnalysis(richControlFlow, new In(i), stable).analyze();
|
||||
NonNullInAnalysis notNullInAnalysis = new NonNullInAnalysis(richControlFlow, new In(i, In.NOT_NULL), stable);
|
||||
Equation<Key, Value> notNullParamEquation = notNullInAnalysis.analyze();
|
||||
possibleNPE = notNullInAnalysis.possibleNPE;
|
||||
notNullParam = notNullParamEquation.rhs.equals(FINAL_NOT_NULL);
|
||||
result.add(notNullParamEquation);
|
||||
}
|
||||
else {
|
||||
// parameter is not leaking, so it is definitely NOT @NotNull
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i), stable), FINAL_TOP));
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NOT_NULL), stable), FINAL_TOP));
|
||||
}
|
||||
if (leakingNullableParameters[i]) {
|
||||
if (notNullParam || possibleNPE) {
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NULLABLE), stable), FINAL_TOP));
|
||||
}
|
||||
else {
|
||||
result.add(new NullableInAnalysis(richControlFlow, new In(i, In.NULLABLE), stable).analyze());
|
||||
}
|
||||
}
|
||||
else {
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NULLABLE), stable), FINAL_NULL));
|
||||
}
|
||||
}
|
||||
if (isReferenceArg && isInterestingResult) {
|
||||
if (leakingParameters[i]) {
|
||||
if (origins.getValue() != null) {
|
||||
// result origins analysis was ok
|
||||
if (!notNullParam) {
|
||||
// may be null on some branch, running "null->..." analysis
|
||||
result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.Null), origins.getValue(), stable).analyze());
|
||||
}
|
||||
else {
|
||||
// @NotNull, so "null->fail"
|
||||
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.Null), stable), FINAL_BOT));
|
||||
}
|
||||
result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.NotNull), origins.getValue(), stable).analyze());
|
||||
if (!notNullParam) {
|
||||
// may be null on some branch, running "null->..." analysis
|
||||
result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.Null), origins, stable).analyze());
|
||||
}
|
||||
else {
|
||||
// result origins analysis failed, approximating to Top
|
||||
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.Null), stable), FINAL_TOP));
|
||||
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.NotNull), stable), FINAL_TOP));
|
||||
// @NotNull, so "null->fail"
|
||||
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.Null), stable), FINAL_BOT));
|
||||
}
|
||||
result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.NotNull), origins, stable).analyze());
|
||||
}
|
||||
else {
|
||||
// parameter is not leaking, so a contract is the same as for the whole method
|
||||
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.Null), stable), outEquation.getValue().rhs));
|
||||
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.NotNull), stable), outEquation.getValue().rhs));
|
||||
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.Null), stable), outEquation.rhs));
|
||||
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.NotNull), stable), outEquation.rhs));
|
||||
}
|
||||
}
|
||||
if (ASMUtils.isBooleanType(argumentTypes[i]) && isInterestingResult) {
|
||||
if (leakingParameters[i]) {
|
||||
if (origins.getValue() != null) {
|
||||
// result origins analysis was ok
|
||||
result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.False), origins.getValue(), stable).analyze());
|
||||
result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.True), origins.getValue(), stable).analyze());
|
||||
}
|
||||
else {
|
||||
// result origins analysis failed, approximating to Top
|
||||
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.False), stable), FINAL_TOP));
|
||||
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.True), stable), FINAL_TOP));
|
||||
}
|
||||
result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.False), origins, stable).analyze());
|
||||
result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.True), origins, stable).analyze());
|
||||
}
|
||||
else {
|
||||
// parameter is not leaking, so a contract is the same as for the whole method
|
||||
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.False), stable), outEquation.getValue().rhs));
|
||||
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.True), stable), outEquation.getValue().rhs));
|
||||
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.False), stable), outEquation.rhs));
|
||||
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.True), stable), outEquation.rhs));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,22 +274,24 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
}
|
||||
|
||||
private List<Equation<Key, Value>> processNonBranchingMethod(Method method,
|
||||
Type[] argumentTypes,
|
||||
ControlFlowGraph graph,
|
||||
boolean isReferenceResult,
|
||||
boolean isBooleanResult,
|
||||
boolean stable) throws AnalyzerException {
|
||||
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 3 + 1);
|
||||
CombinedSingleAnalysis analyzer = new CombinedSingleAnalysis(method, graph);
|
||||
Type[] argumentTypes,
|
||||
ControlFlowGraph graph,
|
||||
boolean isReferenceResult,
|
||||
boolean isBooleanResult,
|
||||
boolean stable) throws AnalyzerException {
|
||||
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 4 + 2);
|
||||
CombinedAnalysis analyzer = new CombinedAnalysis(method, graph);
|
||||
analyzer.analyze();
|
||||
if (isReferenceResult) {
|
||||
result.add(analyzer.outContractEquation(stable));
|
||||
result.add(analyzer.nullableResultEquation(stable));
|
||||
}
|
||||
for (int i = 0; i < argumentTypes.length; i++) {
|
||||
Type argType = argumentTypes[i];
|
||||
boolean isRefArg = ASMUtils.isReferenceType(argType);
|
||||
if (isRefArg) {
|
||||
result.add(analyzer.notNullParamEquation(i, stable));
|
||||
result.add(analyzer.nullableParamEquation(i, stable));
|
||||
}
|
||||
if (isRefArg && (isReferenceResult || isBooleanResult)) {
|
||||
result.add(analyzer.contractEquation(i, Value.Null, stable));
|
||||
@@ -321,13 +306,14 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
}
|
||||
|
||||
private List<Equation<Key, Value>> topEquations(Method method,
|
||||
Type[] argumentTypes,
|
||||
boolean isReferenceResult,
|
||||
boolean isInterestingResult,
|
||||
boolean stable) {
|
||||
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 3 + 1);
|
||||
Type[] argumentTypes,
|
||||
boolean isReferenceResult,
|
||||
boolean isInterestingResult,
|
||||
boolean stable) {
|
||||
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 4 + 2);
|
||||
if (isReferenceResult) {
|
||||
result.add(new Equation<Key, Value>(new Key(method, new Out(), stable), FINAL_TOP));
|
||||
result.add(new Equation<Key, Value>(new Key(method, Out, stable), FINAL_TOP));
|
||||
result.add(new Equation<Key, Value>(new Key(method, NullableOut, stable), FINAL_BOT));
|
||||
}
|
||||
for (int i = 0; i < argumentTypes.length; i++) {
|
||||
Type argType = argumentTypes[i];
|
||||
@@ -335,7 +321,8 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
boolean isBooleanArg = ASMUtils.isBooleanType(argType);
|
||||
|
||||
if (isReferenceArg) {
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i), stable), FINAL_TOP));
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NOT_NULL), stable), FINAL_TOP));
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NULLABLE), stable), FINAL_TOP));
|
||||
}
|
||||
if (isReferenceArg && isInterestingResult) {
|
||||
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.Null), stable), FINAL_TOP));
|
||||
|
||||
+275
-75
@@ -28,34 +28,122 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.BasicInterpreter;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.intellij.codeInspection.bytecodeAnalysis.AbstractValues.*;
|
||||
import static org.jetbrains.org.objectweb.asm.Opcodes.*;
|
||||
import static com.intellij.codeInspection.bytecodeAnalysis.Direction.*;
|
||||
import static com.intellij.codeInspection.bytecodeAnalysis.CombinedData.*;
|
||||
|
||||
final class CombinedCall extends BasicValue {
|
||||
final Method method;
|
||||
final boolean stableCall;
|
||||
final List<? extends BasicValue> args;
|
||||
// additional data structures for combined analysis
|
||||
interface CombinedData {
|
||||
|
||||
CombinedCall(Type tp, Method method, boolean stableCall, List<? extends BasicValue> args) {
|
||||
super(tp);
|
||||
this.method = method;
|
||||
this.stableCall = stableCall;
|
||||
this.args = args;
|
||||
final class ParamKey {
|
||||
final Method method;
|
||||
final int i;
|
||||
final boolean stable;
|
||||
|
||||
ParamKey(Method method, int i, boolean stable) {
|
||||
this.method = method;
|
||||
this.i = i;
|
||||
this.stable = stable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
ParamKey paramKey = (ParamKey)o;
|
||||
|
||||
if (i != paramKey.i) return false;
|
||||
if (stable != paramKey.stable) return false;
|
||||
if (!method.equals(paramKey.method)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = method.hashCode();
|
||||
result = 31 * result + i;
|
||||
result = 31 * result + (stable ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// value knowing at which instruction it was created
|
||||
interface Trackable {
|
||||
int getOriginInsnIndex();
|
||||
}
|
||||
|
||||
final class TrackableCallValue extends BasicValue implements Trackable {
|
||||
private final int originInsnIndex;
|
||||
final Method method;
|
||||
final List<? extends BasicValue> args;
|
||||
final boolean stableCall;
|
||||
final boolean thisCall;
|
||||
|
||||
TrackableCallValue(int originInsnIndex, Type tp, Method method, List<? extends BasicValue> args, boolean stableCall, boolean thisCall) {
|
||||
super(tp);
|
||||
this.originInsnIndex = originInsnIndex;
|
||||
this.method = method;
|
||||
this.args = args;
|
||||
this.stableCall = stableCall;
|
||||
this.thisCall = thisCall;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOriginInsnIndex() {
|
||||
return originInsnIndex;
|
||||
}
|
||||
}
|
||||
|
||||
final class NthParamValue extends BasicValue {
|
||||
final int n;
|
||||
|
||||
public NthParamValue(Type type, int n) {
|
||||
super(type);
|
||||
this.n = n;
|
||||
}
|
||||
}
|
||||
|
||||
final class TrackableNullValue extends BasicValue implements Trackable {
|
||||
static final Type NullType = Type.getObjectType("null");
|
||||
private final int originInsnIndex;
|
||||
public TrackableNullValue(int originInsnIndex) {
|
||||
super(NullType);
|
||||
this.originInsnIndex = originInsnIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOriginInsnIndex() {
|
||||
return originInsnIndex;
|
||||
}
|
||||
}
|
||||
|
||||
final class TrackableValue extends BasicValue implements Trackable {
|
||||
private final int originInsnIndex;
|
||||
|
||||
public TrackableValue(int originInsnIndex, Type type) {
|
||||
super(type);
|
||||
this.originInsnIndex = originInsnIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOriginInsnIndex() {
|
||||
return originInsnIndex;
|
||||
}
|
||||
}
|
||||
|
||||
BasicValue ThisValue = new BasicValue(Type.getObjectType("java/lang/Object"));
|
||||
}
|
||||
|
||||
final class NParamValue extends BasicValue {
|
||||
final int n;
|
||||
public NParamValue(Type type, int n) {
|
||||
super(type);
|
||||
this.n = n;
|
||||
}
|
||||
}
|
||||
// specialized class for analyzing methods without branching in single pass
|
||||
final class CombinedAnalysis {
|
||||
|
||||
final class CombinedSingleAnalysis {
|
||||
private final ControlFlowGraph controlFlow;
|
||||
private final Method method;
|
||||
private final CombinedInterpreter interpreter;
|
||||
@@ -63,11 +151,11 @@ final class CombinedSingleAnalysis {
|
||||
private boolean exception;
|
||||
private final MethodNode methodNode;
|
||||
|
||||
CombinedSingleAnalysis(Method method, ControlFlowGraph controlFlow) {
|
||||
CombinedAnalysis(Method method, ControlFlowGraph controlFlow) {
|
||||
this.method = method;
|
||||
this.controlFlow = controlFlow;
|
||||
methodNode = controlFlow.methodNode;
|
||||
interpreter = new CombinedInterpreter(Type.getArgumentTypes(methodNode.desc).length);
|
||||
interpreter = new CombinedInterpreter(methodNode.instructions, Type.getArgumentTypes(methodNode.desc).length);
|
||||
}
|
||||
|
||||
final void analyze() throws AnalyzerException {
|
||||
@@ -106,18 +194,44 @@ final class CombinedSingleAnalysis {
|
||||
}
|
||||
|
||||
final Equation<Key, Value> notNullParamEquation(int i, boolean stable) {
|
||||
final Key key = new Key(method, new In(i), stable);
|
||||
final Key key = new Key(method, new In(i, In.NOT_NULL), stable);
|
||||
final Result<Key, Value> result;
|
||||
if (interpreter.dereferenced[i]) {
|
||||
if (interpreter.dereferencedParams[i]) {
|
||||
result = new Final<Key, Value>(Value.NotNull);
|
||||
}
|
||||
else {
|
||||
Set<Key> calls = interpreter.callDerefs[i];
|
||||
Set<ParamKey> calls = interpreter.parameterFlow[i];
|
||||
if (calls == null || calls.isEmpty()) {
|
||||
result = new Final<Key, Value>(Value.Top);
|
||||
}
|
||||
else {
|
||||
result = new Pending<Key, Value>(new SingletonSet<Product<Key, Value>>(new Product<Key, Value>(Value.Top, calls)));
|
||||
Set<Key> keys = new HashSet<Key>();
|
||||
for (ParamKey pk: calls) {
|
||||
keys.add(new Key(pk.method, new In(pk.i, In.NOT_NULL), pk.stable));
|
||||
}
|
||||
result = new Pending<Key, Value>(new SingletonSet<Product<Key, Value>>(new Product<Key, Value>(Value.Top, keys)));
|
||||
}
|
||||
}
|
||||
return new Equation<Key, Value>(key, result);
|
||||
}
|
||||
|
||||
final Equation<Key, Value> nullableParamEquation(int i, boolean stable) {
|
||||
final Key key = new Key(method, new In(i, In.NULLABLE), stable);
|
||||
final Result<Key, Value> result;
|
||||
if (interpreter.dereferencedParams[i] || interpreter.notNullableParams[i] || returnValue instanceof NthParamValue && ((NthParamValue)returnValue).n == i) {
|
||||
result = new Final<Key, Value>(Value.Top);
|
||||
}
|
||||
else {
|
||||
Set<ParamKey> calls = interpreter.parameterFlow[i];
|
||||
if (calls == null || calls.isEmpty()) {
|
||||
result = new Final<Key, Value>(Value.Null);
|
||||
}
|
||||
else {
|
||||
Set<Product<Key, Value>> sum = new HashSet<Product<Key, Value>>();
|
||||
for (ParamKey pk: calls) {
|
||||
sum.add(new Product<Key, Value>(Value.Top, Collections.singleton(new Key(pk.method, new In(pk.i, In.NULLABLE), pk.stable))));
|
||||
}
|
||||
result = new Pending<Key, Value>(sum);
|
||||
}
|
||||
}
|
||||
return new Equation<Key, Value>(key, result);
|
||||
@@ -126,7 +240,7 @@ final class CombinedSingleAnalysis {
|
||||
final Equation<Key, Value> contractEquation(int i, Value inValue, boolean stable) {
|
||||
final Key key = new Key(method, new InOut(i, inValue), stable);
|
||||
final Result<Key, Value> result;
|
||||
if (exception || (inValue == Value.Null && interpreter.dereferenced[i])) {
|
||||
if (exception || (inValue == Value.Null && interpreter.dereferencedParams[i])) {
|
||||
result = new Final<Key, Value>(Value.Bot);
|
||||
}
|
||||
else if (FalseValue == returnValue) {
|
||||
@@ -135,29 +249,29 @@ final class CombinedSingleAnalysis {
|
||||
else if (TrueValue == returnValue) {
|
||||
result = new Final<Key, Value>(Value.True);
|
||||
}
|
||||
else if (NullValue == returnValue) {
|
||||
else if (returnValue instanceof TrackableNullValue) {
|
||||
result = new Final<Key, Value>(Value.Null);
|
||||
}
|
||||
else if (returnValue instanceof NotNullValue) {
|
||||
else if (returnValue instanceof NotNullValue || ThisValue == returnValue) {
|
||||
result = new Final<Key, Value>(Value.NotNull);
|
||||
}
|
||||
else if (returnValue instanceof NParamValue && ((NParamValue)returnValue).n == i) {
|
||||
else if (returnValue instanceof NthParamValue && ((NthParamValue)returnValue).n == i) {
|
||||
result = new Final<Key, Value>(inValue);
|
||||
}
|
||||
else if (returnValue instanceof CombinedCall) {
|
||||
CombinedCall call = (CombinedCall)returnValue;
|
||||
else if (returnValue instanceof TrackableCallValue) {
|
||||
TrackableCallValue call = (TrackableCallValue)returnValue;
|
||||
HashSet<Key> keys = new HashSet<Key>();
|
||||
for (int argI = 0; argI < call.args.size(); argI++) {
|
||||
BasicValue arg = call.args.get(argI);
|
||||
if (arg instanceof NParamValue) {
|
||||
NParamValue npv = (NParamValue)arg;
|
||||
if (arg instanceof NthParamValue) {
|
||||
NthParamValue npv = (NthParamValue)arg;
|
||||
if (npv.n == i) {
|
||||
keys.add(new Key(call.method, new InOut(argI, inValue), call.stableCall));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ASMUtils.isReferenceType(call.getType())) {
|
||||
keys.add(new Key(call.method, new Out(), call.stableCall));
|
||||
keys.add(new Key(call.method, Out, call.stableCall));
|
||||
}
|
||||
if (keys.isEmpty()) {
|
||||
result = new Final<Key, Value>(Value.Top);
|
||||
@@ -172,7 +286,7 @@ final class CombinedSingleAnalysis {
|
||||
}
|
||||
|
||||
final Equation<Key, Value> outContractEquation(boolean stable) {
|
||||
final Key key = new Key(method, new Out(), stable);
|
||||
final Key key = new Key(method, Out, stable);
|
||||
final Result<Key, Value> result;
|
||||
if (exception) {
|
||||
result = new Final<Key, Value>(Value.Bot);
|
||||
@@ -183,15 +297,15 @@ final class CombinedSingleAnalysis {
|
||||
else if (TrueValue == returnValue) {
|
||||
result = new Final<Key, Value>(Value.True);
|
||||
}
|
||||
else if (NullValue == returnValue) {
|
||||
else if (returnValue instanceof TrackableNullValue) {
|
||||
result = new Final<Key, Value>(Value.Null);
|
||||
}
|
||||
else if (returnValue instanceof NotNullValue) {
|
||||
else if (returnValue instanceof NotNullValue || returnValue == ThisValue) {
|
||||
result = new Final<Key, Value>(Value.NotNull);
|
||||
}
|
||||
else if (returnValue instanceof CombinedCall) {
|
||||
CombinedCall call = (CombinedCall)returnValue;
|
||||
Key callKey = new Key(call.method, new Out(), call.stableCall);
|
||||
else if (returnValue instanceof TrackableCallValue) {
|
||||
TrackableCallValue call = (TrackableCallValue)returnValue;
|
||||
Key callKey = new Key(call.method, Out, call.stableCall);
|
||||
Set<Key> keys = new SingletonSet<Key>(callKey);
|
||||
result = new Pending<Key, Value>(new SingletonSet<Product<Key, Value>>(new Product<Key, Value>(Value.Top, keys)));
|
||||
}
|
||||
@@ -201,6 +315,28 @@ final class CombinedSingleAnalysis {
|
||||
return new Equation<Key, Value>(key, result);
|
||||
}
|
||||
|
||||
final Equation<Key, Value> nullableResultEquation(boolean stable) {
|
||||
final Key key = new Key(method, NullableOut, stable);
|
||||
final Result<Key, Value> result;
|
||||
if (exception ||
|
||||
returnValue instanceof Trackable && interpreter.dereferencedValues[((Trackable)returnValue).getOriginInsnIndex()]) {
|
||||
result = new Final<Key, Value>(Value.Bot);
|
||||
}
|
||||
else if (returnValue instanceof TrackableCallValue) {
|
||||
TrackableCallValue call = (TrackableCallValue)returnValue;
|
||||
Key callKey = new Key(call.method, NullableOut, call.stableCall || call.thisCall);
|
||||
Set<Key> keys = new SingletonSet<Key>(callKey);
|
||||
result = new Pending<Key, Value>(new SingletonSet<Product<Key, Value>>(new Product<Key, Value>(Value.Null, keys)));
|
||||
}
|
||||
else if (returnValue instanceof TrackableNullValue) {
|
||||
result = new Final<Key, Value>(Value.Null);
|
||||
}
|
||||
else {
|
||||
result = new Final<Key, Value>(Value.Bot);
|
||||
}
|
||||
return new Equation<Key, Value>(key, result);
|
||||
}
|
||||
|
||||
final Frame<BasicValue> createStartFrame() {
|
||||
Frame<BasicValue> frame = new Frame<BasicValue>(methodNode.maxLocals, methodNode.maxStack);
|
||||
Type returnType = Type.getReturnType(methodNode.desc);
|
||||
@@ -210,10 +346,10 @@ final class CombinedSingleAnalysis {
|
||||
Type[] args = Type.getArgumentTypes(methodNode.desc);
|
||||
int local = 0;
|
||||
if ((methodNode.access & Opcodes.ACC_STATIC) == 0) {
|
||||
frame.setLocal(local++, new AbstractValues.NotNullValue(Type.getObjectType(controlFlow.className)));
|
||||
frame.setLocal(local++, ThisValue);
|
||||
}
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
BasicValue value = new NParamValue(args[i], i);
|
||||
BasicValue value = new NthParamValue(args[i], i);
|
||||
frame.setLocal(local++, value);
|
||||
if (args[i].getSize() == 2) {
|
||||
frame.setLocal(local++, BasicValue.UNINITIALIZED_VALUE);
|
||||
@@ -227,23 +363,47 @@ final class CombinedSingleAnalysis {
|
||||
}
|
||||
|
||||
final class CombinedInterpreter extends BasicInterpreter {
|
||||
final boolean[] dereferenced;
|
||||
final Set<Key>[] callDerefs;
|
||||
// Parameters dereferenced during execution of a method, tracked by parameter's indices.
|
||||
// Dereferenced parameters are @NotNull.
|
||||
final boolean[] dereferencedParams;
|
||||
// Parameters, that are written to something or passed to an interface methods.
|
||||
// This parameters cannot be @Nullable.
|
||||
final boolean[] notNullableParams;
|
||||
// parameterFlow(i) for i-th parameter stores a set parameter positions it is passed to
|
||||
// parameter is @NotNull if any of its usages are @NotNull
|
||||
final Set<ParamKey>[] parameterFlow;
|
||||
|
||||
CombinedInterpreter(int arity) {
|
||||
dereferenced = new boolean[arity];
|
||||
callDerefs = new Set[arity];
|
||||
// Trackable values that were dereferenced during execution of a method
|
||||
// Values are are identified by `origin` index
|
||||
final boolean[] dereferencedValues;
|
||||
private final InsnList insns;
|
||||
|
||||
CombinedInterpreter(InsnList insns, int arity) {
|
||||
dereferencedParams = new boolean[arity];
|
||||
notNullableParams = new boolean[arity];
|
||||
parameterFlow = new Set[arity];
|
||||
this.insns = insns;
|
||||
dereferencedValues = new boolean[insns.size()];
|
||||
}
|
||||
|
||||
private int insnIndex(AbstractInsnNode insn) {
|
||||
return insns.indexOf(insn);
|
||||
}
|
||||
|
||||
private static BasicValue track(int origin, BasicValue basicValue) {
|
||||
return basicValue == null ? null : new TrackableValue(origin, basicValue.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicValue newOperation(AbstractInsnNode insn) throws AnalyzerException {
|
||||
int origin = insnIndex(insn);
|
||||
switch (insn.getOpcode()) {
|
||||
case ICONST_0:
|
||||
return FalseValue;
|
||||
case ICONST_1:
|
||||
return TrueValue;
|
||||
case ACONST_NULL:
|
||||
return NullValue;
|
||||
return new TrackableNullValue(origin);
|
||||
case LDC:
|
||||
Object cst = ((LdcInsnNode)insn).cst;
|
||||
if (cst instanceof Type) {
|
||||
@@ -266,22 +426,26 @@ final class CombinedInterpreter extends BasicInterpreter {
|
||||
return new NotNullValue(Type.getObjectType(((TypeInsnNode)insn).desc));
|
||||
default:
|
||||
}
|
||||
return super.newOperation(insn);
|
||||
return track(origin, super.newOperation(insn));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicValue unaryOperation(AbstractInsnNode insn, BasicValue value) throws AnalyzerException {
|
||||
int origin = insnIndex(insn);
|
||||
switch (insn.getOpcode()) {
|
||||
case GETFIELD:
|
||||
case ARRAYLENGTH:
|
||||
case MONITORENTER:
|
||||
if (value instanceof NParamValue) {
|
||||
dereferenced[((NParamValue)value).n] = true;
|
||||
if (value instanceof NthParamValue) {
|
||||
dereferencedParams[((NthParamValue)value).n] = true;
|
||||
}
|
||||
return super.unaryOperation(insn, value);
|
||||
if (value instanceof Trackable) {
|
||||
dereferencedValues[((Trackable)value).getOriginInsnIndex()] = true;
|
||||
}
|
||||
return track(origin, super.unaryOperation(insn, value));
|
||||
case CHECKCAST:
|
||||
if (value instanceof NParamValue) {
|
||||
return new NParamValue(Type.getObjectType(((TypeInsnNode)insn).desc), ((NParamValue)value).n);
|
||||
if (value instanceof NthParamValue) {
|
||||
return new NthParamValue(Type.getObjectType(((TypeInsnNode)insn).desc), ((NthParamValue)value).n);
|
||||
}
|
||||
break;
|
||||
case NEWARRAY:
|
||||
@@ -289,12 +453,23 @@ final class CombinedInterpreter extends BasicInterpreter {
|
||||
return new NotNullValue(super.unaryOperation(insn, value).getType());
|
||||
default:
|
||||
}
|
||||
return super.unaryOperation(insn, value);
|
||||
return track(origin, super.unaryOperation(insn, value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicValue binaryOperation(AbstractInsnNode insn, BasicValue value1, BasicValue value2) throws AnalyzerException {
|
||||
switch (insn.getOpcode()) {
|
||||
case PUTFIELD:
|
||||
if (value1 instanceof NthParamValue) {
|
||||
dereferencedParams[((NthParamValue)value1).n] = true;
|
||||
}
|
||||
if (value1 instanceof Trackable) {
|
||||
dereferencedValues[((Trackable)value1).getOriginInsnIndex()] = true;
|
||||
}
|
||||
if (value2 instanceof NthParamValue) {
|
||||
notNullableParams[((NthParamValue)value2).n] = true;
|
||||
}
|
||||
break;
|
||||
case IALOAD:
|
||||
case LALOAD:
|
||||
case FALOAD:
|
||||
@@ -303,14 +478,16 @@ final class CombinedInterpreter extends BasicInterpreter {
|
||||
case BALOAD:
|
||||
case CALOAD:
|
||||
case SALOAD:
|
||||
case PUTFIELD:
|
||||
if (value1 instanceof NParamValue) {
|
||||
dereferenced[((NParamValue)value1).n] = true;
|
||||
if (value1 instanceof NthParamValue) {
|
||||
dereferencedParams[((NthParamValue)value1).n] = true;
|
||||
}
|
||||
if (value1 instanceof Trackable) {
|
||||
dereferencedValues[((Trackable)value1).getOriginInsnIndex()] = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return super.binaryOperation(insn, value1, value2);
|
||||
return track(insnIndex(insn), super.binaryOperation(insn, value1, value2));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -321,33 +498,51 @@ final class CombinedInterpreter extends BasicInterpreter {
|
||||
case LASTORE:
|
||||
case FASTORE:
|
||||
case DASTORE:
|
||||
case AASTORE:
|
||||
case BASTORE:
|
||||
case CASTORE:
|
||||
case SASTORE:
|
||||
if (value1 instanceof NParamValue) {
|
||||
dereferenced[((NParamValue)value1).n] = true;
|
||||
if (value1 instanceof NthParamValue) {
|
||||
dereferencedParams[((NthParamValue)value1).n] = true;
|
||||
}
|
||||
if (value1 instanceof Trackable) {
|
||||
dereferencedValues[((Trackable)value1).getOriginInsnIndex()] = true;
|
||||
}
|
||||
break;
|
||||
case AASTORE:
|
||||
if (value1 instanceof NthParamValue) {
|
||||
dereferencedParams[((NthParamValue)value1).n] = true;
|
||||
}
|
||||
if (value1 instanceof Trackable) {
|
||||
dereferencedValues[((Trackable)value1).getOriginInsnIndex()] = true;
|
||||
}
|
||||
if (value3 instanceof NthParamValue) {
|
||||
notNullableParams[((NthParamValue)value3).n] = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return super.ternaryOperation(insn, value1, value2, value3);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicValue naryOperation(AbstractInsnNode insn, List<? extends BasicValue> values) throws AnalyzerException {
|
||||
int opCode = insn.getOpcode();
|
||||
int shift = opCode == INVOKESTATIC ? 0 : 1;
|
||||
|
||||
int origin = insnIndex(insn);
|
||||
switch (opCode) {
|
||||
case INVOKESPECIAL:
|
||||
case INVOKEINTERFACE:
|
||||
case INVOKEVIRTUAL:
|
||||
if (values.get(0) instanceof NParamValue) {
|
||||
dereferenced[((NParamValue)values.get(0)).n] = true;
|
||||
BasicValue receiver = values.get(0);
|
||||
if (receiver instanceof NthParamValue) {
|
||||
dereferencedParams[((NthParamValue)receiver).n] = true;
|
||||
}
|
||||
if (receiver instanceof Trackable) {
|
||||
dereferencedValues[((Trackable)receiver).getOriginInsnIndex()] = true;
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
|
||||
switch (opCode) {
|
||||
case INVOKESTATIC:
|
||||
case INVOKESPECIAL:
|
||||
@@ -359,26 +554,31 @@ final class CombinedInterpreter extends BasicInterpreter {
|
||||
Type retType = Type.getReturnType(mNode.desc);
|
||||
|
||||
for (int i = shift; i < values.size(); i++) {
|
||||
if (values.get(i) instanceof NParamValue) {
|
||||
if (opCode != INVOKEINTERFACE) {
|
||||
int n = ((NParamValue)values.get(i)).n;
|
||||
Set<Key> npKeys = callDerefs[n];
|
||||
if (values.get(i) instanceof NthParamValue) {
|
||||
int n = ((NthParamValue)values.get(i)).n;
|
||||
if (opCode == INVOKEINTERFACE) {
|
||||
notNullableParams[n] = true;
|
||||
}
|
||||
else {
|
||||
Set<ParamKey> npKeys = parameterFlow[n];
|
||||
if (npKeys == null) {
|
||||
npKeys = new HashSet<Key>();
|
||||
callDerefs[n] = npKeys;
|
||||
npKeys = new HashSet<ParamKey>();
|
||||
parameterFlow[n] = npKeys;
|
||||
}
|
||||
npKeys.add(new Key(method, new In(i - shift), stable));
|
||||
npKeys.add(new ParamKey(method, i - shift, stable));
|
||||
}
|
||||
}
|
||||
}
|
||||
BasicValue receiver = null;
|
||||
if (shift == 1) {
|
||||
values.remove(0);
|
||||
receiver = values.remove(0);
|
||||
}
|
||||
return new CombinedCall(retType, method, stable, values);
|
||||
boolean thisCall = (opCode == INVOKEINTERFACE || opCode == INVOKEVIRTUAL) && receiver == ThisValue;
|
||||
return new TrackableCallValue(origin, retType, method, values, stable, thisCall);
|
||||
case MULTIANEWARRAY:
|
||||
return new NotNullValue(super.naryOperation(insn, values).getType());
|
||||
default:
|
||||
}
|
||||
return super.naryOperation(insn, values);
|
||||
return track(origin, super.naryOperation(insn, values));
|
||||
}
|
||||
}
|
||||
|
||||
+5
-11
@@ -34,20 +34,14 @@ import java.util.Set;
|
||||
|
||||
import static com.intellij.codeInspection.bytecodeAnalysis.AbstractValues.*;
|
||||
import static org.jetbrains.org.objectweb.asm.Opcodes.*;
|
||||
import static com.intellij.codeInspection.bytecodeAnalysis.Direction.*;
|
||||
|
||||
class InOutAnalysis extends Analysis<Result<Key, Value>> {
|
||||
|
||||
private static final ThreadLocal<State[]> ourPending = new ThreadLocal<State[]>() {
|
||||
@Override
|
||||
protected State[] initialValue() {
|
||||
return new State[Analysis.STEPS_LIMIT];
|
||||
}
|
||||
};
|
||||
|
||||
static final ResultUtil<Key, Value> resultUtil =
|
||||
new ResultUtil<Key, Value>(new ELattice<Value>(Value.Bot, Value.Top));
|
||||
|
||||
final private State[] pending = ourPending.get();
|
||||
final private State[] pending = ourPendingStates.get();
|
||||
private final InOutInterpreter interpreter;
|
||||
private final Value inValue;
|
||||
private final int generalizeShift;
|
||||
@@ -403,7 +397,7 @@ class InOutInterpreter extends BasicInterpreter {
|
||||
}
|
||||
default:
|
||||
}
|
||||
return super.ternaryOperation(insn, value1, value2, value3);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -443,7 +437,7 @@ class InOutInterpreter extends BasicInterpreter {
|
||||
}
|
||||
}
|
||||
if (isRefRetType) {
|
||||
keys.add(new Key(method, new Out(), stable));
|
||||
keys.add(new Key(method, Out, stable));
|
||||
}
|
||||
if (!keys.isEmpty()) {
|
||||
return new CallResultValue(retType, keys);
|
||||
@@ -451,7 +445,7 @@ class InOutInterpreter extends BasicInterpreter {
|
||||
}
|
||||
else if (isRefRetType) {
|
||||
HashSet<Key> keys = new HashSet<Key>();
|
||||
keys.add(new Key(method, new Out(), stable));
|
||||
keys.add(new Key(method, Out, stable));
|
||||
return new CallResultValue(retType, keys);
|
||||
}
|
||||
}
|
||||
|
||||
+94
-126
@@ -53,136 +53,108 @@ enum Value {
|
||||
}
|
||||
|
||||
interface Direction {
|
||||
static final int OUT_DIRECTION = 0;
|
||||
static final int IN_DIRECTION = 1;
|
||||
static final int INOUT_DIRECTION = 2;
|
||||
int directionId();
|
||||
int paramId();
|
||||
int valueId();
|
||||
}
|
||||
final class In implements Direction {
|
||||
static final int NOT_NULL = 0;
|
||||
static final int NULLABLE = 1;
|
||||
final int paramIndex;
|
||||
final int nullityMask;
|
||||
|
||||
final class In implements Direction {
|
||||
final int paramIndex;
|
||||
In(int paramIndex, int nullityMask) {
|
||||
this.paramIndex = paramIndex;
|
||||
this.nullityMask = nullityMask;
|
||||
}
|
||||
|
||||
In(int paramIndex) {
|
||||
this.paramIndex = paramIndex;
|
||||
@Override
|
||||
public String toString() {
|
||||
return "In " + paramIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
In in = (In)o;
|
||||
if (paramIndex != in.paramIndex) return false;
|
||||
if (nullityMask != in.nullityMask) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * paramIndex + nullityMask;
|
||||
}
|
||||
|
||||
public int paramId() {
|
||||
return paramIndex;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "In " + paramIndex;
|
||||
final class InOut implements Direction {
|
||||
final int paramIndex;
|
||||
final Value inValue;
|
||||
|
||||
InOut(int paramIndex, Value inValue) {
|
||||
this.paramIndex = paramIndex;
|
||||
this.inValue = inValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
InOut inOut = (InOut)o;
|
||||
|
||||
if (paramIndex != inOut.paramIndex) return false;
|
||||
if (inValue != inOut.inValue) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = paramIndex;
|
||||
result = 31 * result + inValue.ordinal();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InOut " + paramIndex + " " + inValue.toString();
|
||||
}
|
||||
|
||||
public int paramId() {
|
||||
return paramIndex;
|
||||
}
|
||||
|
||||
public int valueId() {
|
||||
return inValue.ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
In in = (In) o;
|
||||
if (paramIndex != in.paramIndex) return false;
|
||||
return true;
|
||||
}
|
||||
Direction Out = new Direction() {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Out";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return paramIndex;
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int directionId() {
|
||||
return IN_DIRECTION;
|
||||
}
|
||||
Direction NullableOut = new Direction() {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NullableOut";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int paramId() {
|
||||
return paramIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int valueId() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
final class InOut implements Direction {
|
||||
final int paramIndex;
|
||||
final Value inValue;
|
||||
|
||||
InOut(int paramIndex, Value inValue) {
|
||||
this.paramIndex = paramIndex;
|
||||
this.inValue = inValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
InOut inOut = (InOut) o;
|
||||
|
||||
if (paramIndex != inOut.paramIndex) return false;
|
||||
if (inValue != inOut.inValue) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = paramIndex;
|
||||
result = 31 * result + inValue.ordinal();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InOut " + paramIndex + " " + inValue.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int directionId() {
|
||||
return INOUT_DIRECTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int paramId() {
|
||||
return paramIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int valueId() {
|
||||
return inValue.ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
final class Out implements Direction {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Out";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof Out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int directionId() {
|
||||
return OUT_DIRECTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int paramId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int valueId() {
|
||||
return 0;
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return -2;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
final class Key {
|
||||
@@ -219,10 +191,6 @@ final class Key {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "" + method + ' ' + direction + ' ' + stable;
|
||||
return method + " " + direction + " " + stable;
|
||||
}
|
||||
}
|
||||
|
||||
class LimitReachedException extends RuntimeException {
|
||||
|
||||
}
|
||||
|
||||
@@ -63,6 +63,10 @@ final class HKey {
|
||||
return stable ? this : new HKey(key, dirKey, true);
|
||||
}
|
||||
|
||||
HKey mkUnstable() {
|
||||
return stable ? new HKey(key, dirKey, false) : this;
|
||||
}
|
||||
|
||||
public HKey mkBase() {
|
||||
return dirKey == 0 ? this : new HKey(key, 0, stable);
|
||||
}
|
||||
|
||||
+423
@@ -0,0 +1,423 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.asm.AnalyzerExt;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.asm.InterpreterExt;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.asm.LiteAnalyzerExt;
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes;
|
||||
import org.jetbrains.org.objectweb.asm.Type;
|
||||
import org.jetbrains.org.objectweb.asm.tree.*;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicInterpreter;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.intellij.codeInspection.bytecodeAnalysis.NullableMethodAnalysisData.*;
|
||||
|
||||
interface NullableMethodAnalysisData {
|
||||
Type NullType = Type.getObjectType("null");
|
||||
Type ThisType = Type.getObjectType("this");
|
||||
Type CallType = Type.getObjectType("/Call");
|
||||
|
||||
final class LabeledNull extends BasicValue {
|
||||
final int origins;
|
||||
|
||||
public LabeledNull(int origins) {
|
||||
super(NullType);
|
||||
this.origins = origins;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
LabeledNull that = (LabeledNull)o;
|
||||
return origins == that.origins;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return origins;
|
||||
}
|
||||
}
|
||||
|
||||
final class Calls extends BasicValue {
|
||||
final int mergedLabels;
|
||||
|
||||
public Calls(int mergedLabels) {
|
||||
super(CallType);
|
||||
this.mergedLabels = mergedLabels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Calls calls = (Calls)o;
|
||||
return mergedLabels == calls.mergedLabels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mergedLabels;
|
||||
}
|
||||
}
|
||||
|
||||
final class Constraint {
|
||||
final static Constraint EMPTY = new Constraint(0, 0);
|
||||
|
||||
final int calls;
|
||||
final int nulls;
|
||||
|
||||
public Constraint(int calls, int nulls) {
|
||||
this.calls = calls;
|
||||
this.nulls = nulls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Constraint that = (Constraint)o;
|
||||
|
||||
if (calls != that.calls) return false;
|
||||
if (nulls != that.nulls) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = calls;
|
||||
result = 31 * result + nulls;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
BasicValue ThisValue = new BasicValue(ThisType);
|
||||
}
|
||||
|
||||
class NullableMethodAnalysis {
|
||||
|
||||
static Result<Key, Value> FinalNull = new Final<Key, Value>(Value.Null);
|
||||
static Result<Key, Value> FinalBot = new Final<Key, Value>(Value.Bot);
|
||||
static BasicValue lNull = new LabeledNull(0);
|
||||
|
||||
static Result<Key, Value> analyze(MethodNode methodNode, boolean[] origins, boolean jsr) throws AnalyzerException {
|
||||
InsnList insns = methodNode.instructions;
|
||||
Constraint[] data = new Constraint[insns.size()];
|
||||
int[] originsMapping = mapOrigins(origins);
|
||||
|
||||
NullableMethodInterpreter interpreter = new NullableMethodInterpreter(insns, origins, originsMapping);
|
||||
Frame<BasicValue>[] frames =
|
||||
jsr ?
|
||||
new AnalyzerExt<BasicValue, Constraint, NullableMethodInterpreter>(interpreter, data, Constraint.EMPTY).analyze("this", methodNode) :
|
||||
new LiteAnalyzerExt<BasicValue, Constraint, NullableMethodInterpreter>(interpreter, data, Constraint.EMPTY).analyze("this", methodNode);
|
||||
|
||||
BasicValue result = BasicValue.REFERENCE_VALUE;
|
||||
for (int i = 0; i < frames.length; i++) {
|
||||
Frame<BasicValue> frame = frames[i];
|
||||
if (frame != null && insns.get(i).getOpcode() == Opcodes.ARETURN) {
|
||||
BasicValue stackTop = frame.pop();
|
||||
result = combine(result, stackTop, data[i]);
|
||||
}
|
||||
}
|
||||
if (result instanceof LabeledNull) {
|
||||
return FinalNull;
|
||||
}
|
||||
if (result instanceof Calls) {
|
||||
Calls calls = ((Calls)result);
|
||||
int mergedMappedLabels = calls.mergedLabels;
|
||||
if (mergedMappedLabels != 0) {
|
||||
Set<Product<Key, Value>> sum = new HashSet<Product<Key, Value>>();
|
||||
Key[] createdKeys = interpreter.keys;
|
||||
for (int origin = 0; origin < originsMapping.length; origin++) {
|
||||
int mappedOrigin = originsMapping[origin];
|
||||
Key createdKey = createdKeys[origin];
|
||||
if (createdKey != null && (mergedMappedLabels & (1 << mappedOrigin)) != 0) {
|
||||
sum.add(new Product<Key, Value>(Value.Null, Collections.singleton(createdKey)));
|
||||
}
|
||||
}
|
||||
if (!sum.isEmpty()) {
|
||||
return new Pending<Key, Value>(sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
return FinalBot;
|
||||
}
|
||||
|
||||
private static int[] mapOrigins(boolean[] origins) {
|
||||
int[] originsMapping = new int[origins.length];
|
||||
int mapped = 0;
|
||||
for (int i = 0; i < origins.length; i++) {
|
||||
originsMapping[i] = origins[i] ? mapped++ : -1;
|
||||
}
|
||||
return originsMapping;
|
||||
}
|
||||
|
||||
static BasicValue combine(BasicValue v1, BasicValue v2, Constraint constraint) {
|
||||
if (v1 instanceof LabeledNull) {
|
||||
return lNull;
|
||||
}
|
||||
else if (v2 instanceof LabeledNull) {
|
||||
int v2Origins = ((LabeledNull)v2).origins;
|
||||
int constraintOrigins = constraint.nulls;
|
||||
int intersect = v2Origins & constraintOrigins;
|
||||
return intersect == v2Origins ? v1 : lNull;
|
||||
}
|
||||
else if (v1 instanceof Calls) {
|
||||
if (v2 instanceof Calls) {
|
||||
Calls calls1 = (Calls)v1;
|
||||
Calls calls2 = (Calls)v2;
|
||||
int labels2 = calls2.mergedLabels;
|
||||
int aliveLabels2 = labels2 - (labels2 & constraint.calls);
|
||||
return new Calls(calls1.mergedLabels | aliveLabels2);
|
||||
} else {
|
||||
return v1;
|
||||
}
|
||||
}
|
||||
else if (v2 instanceof Calls) {
|
||||
Calls calls2 = (Calls)v2;
|
||||
int labels2 = calls2.mergedLabels;
|
||||
int aliveLabels2 = labels2 - (labels2 & constraint.calls);
|
||||
return new Calls(aliveLabels2);
|
||||
}
|
||||
return BasicValue.REFERENCE_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
class NullableMethodInterpreter extends BasicInterpreter implements InterpreterExt<Constraint> {
|
||||
final InsnList insns;
|
||||
final boolean[] origins;
|
||||
private final int[] originsMapping;
|
||||
final Key[] keys;
|
||||
|
||||
Constraint constraint = null;
|
||||
int delta = 0;
|
||||
int nullsDelta = 0;
|
||||
int notNullInsn = -1;
|
||||
int notNullCall = 0;
|
||||
int notNullNull = 0;
|
||||
|
||||
NullableMethodInterpreter(InsnList insns, boolean[] origins, int[] originsMapping) {
|
||||
this.insns = insns;
|
||||
this.origins = origins;
|
||||
this.originsMapping = originsMapping;
|
||||
keys = new Key[originsMapping.length];
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicValue newValue(Type type) {
|
||||
return ThisType.equals(type) ? ThisValue : super.newValue(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicValue newOperation(AbstractInsnNode insn) throws AnalyzerException {
|
||||
if (insn.getOpcode() == Opcodes.ACONST_NULL) {
|
||||
int insnIndex = insns.indexOf(insn);
|
||||
if (origins[insnIndex]) {
|
||||
return new LabeledNull(1 << originsMapping[insnIndex]);
|
||||
}
|
||||
}
|
||||
return super.newOperation(insn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicValue unaryOperation(AbstractInsnNode insn, BasicValue value) throws AnalyzerException {
|
||||
switch (insn.getOpcode()) {
|
||||
case GETFIELD:
|
||||
case ARRAYLENGTH:
|
||||
case MONITORENTER:
|
||||
if (value instanceof Calls) {
|
||||
delta = ((Calls)value).mergedLabels;
|
||||
}
|
||||
break;
|
||||
case IFNULL:
|
||||
if (value instanceof Calls) {
|
||||
notNullInsn = insns.indexOf(insn) + 1;
|
||||
notNullCall = ((Calls)value).mergedLabels;
|
||||
}
|
||||
else if (value instanceof LabeledNull) {
|
||||
notNullInsn = insns.indexOf(insn) + 1;
|
||||
notNullNull = ((LabeledNull)value).origins;
|
||||
}
|
||||
break;
|
||||
case IFNONNULL:
|
||||
if (value instanceof Calls) {
|
||||
notNullInsn = insns.indexOf(((JumpInsnNode)insn).label);
|
||||
notNullCall = ((Calls)value).mergedLabels;
|
||||
}
|
||||
else if (value instanceof LabeledNull) {
|
||||
notNullInsn = insns.indexOf(((JumpInsnNode)insn).label);
|
||||
notNullNull = ((LabeledNull)value).origins;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
}
|
||||
return super.unaryOperation(insn, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicValue binaryOperation(AbstractInsnNode insn, BasicValue value1, BasicValue value2) throws AnalyzerException {
|
||||
switch (insn.getOpcode()) {
|
||||
case PUTFIELD:
|
||||
case IALOAD:
|
||||
case LALOAD:
|
||||
case FALOAD:
|
||||
case DALOAD:
|
||||
case AALOAD:
|
||||
case BALOAD:
|
||||
case CALOAD:
|
||||
case SALOAD:
|
||||
if (value1 instanceof Calls) {
|
||||
delta = ((Calls)value1).mergedLabels;
|
||||
}
|
||||
if (value1 instanceof LabeledNull){
|
||||
nullsDelta = ((LabeledNull)value1).origins;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return super.binaryOperation(insn, value1, value2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicValue ternaryOperation(AbstractInsnNode insn, BasicValue value1, BasicValue value2, BasicValue value3)
|
||||
throws AnalyzerException {
|
||||
if (value1 instanceof Calls) {
|
||||
delta = ((Calls)value1).mergedLabels;
|
||||
}
|
||||
if (value1 instanceof LabeledNull){
|
||||
nullsDelta = ((LabeledNull)value1).origins;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicValue naryOperation(AbstractInsnNode insn, List<? extends BasicValue> values) throws AnalyzerException {
|
||||
int opCode = insn.getOpcode();
|
||||
switch (opCode) {
|
||||
case INVOKESPECIAL:
|
||||
case INVOKEINTERFACE:
|
||||
case INVOKEVIRTUAL:
|
||||
BasicValue receiver = values.get(0);
|
||||
if (receiver instanceof Calls) {
|
||||
delta = ((Calls)receiver).mergedLabels;
|
||||
}
|
||||
if (receiver instanceof LabeledNull){
|
||||
nullsDelta = ((LabeledNull)receiver).origins;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
switch (opCode) {
|
||||
case INVOKESTATIC:
|
||||
case INVOKESPECIAL:
|
||||
case INVOKEVIRTUAL:
|
||||
int insnIndex = insns.indexOf(insn);
|
||||
if (origins[insnIndex]) {
|
||||
boolean stable = (opCode == INVOKESTATIC) ||
|
||||
(opCode == INVOKESPECIAL) ||
|
||||
(values.get(0) == ThisValue);
|
||||
MethodInsnNode mNode = ((MethodInsnNode)insn);
|
||||
Method method = new Method(mNode.owner, mNode.name, mNode.desc);
|
||||
int label = 1 << originsMapping[insnIndex];
|
||||
if (keys[insnIndex] == null) {
|
||||
keys[insnIndex] = new Key(method, Direction.NullableOut, stable);
|
||||
}
|
||||
return new Calls(label);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return super.naryOperation(insn, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicValue merge(BasicValue v1, BasicValue v2) {
|
||||
if (v1 instanceof LabeledNull) {
|
||||
if (v2 instanceof LabeledNull) {
|
||||
return new LabeledNull(((LabeledNull)v1).origins | ((LabeledNull)v2).origins);
|
||||
}
|
||||
else {
|
||||
return v1;
|
||||
}
|
||||
}
|
||||
else if (v2 instanceof LabeledNull) {
|
||||
return v2;
|
||||
}
|
||||
else if (v1 instanceof Calls) {
|
||||
if (v2 instanceof Calls) {
|
||||
Calls calls1 = (Calls)v1;
|
||||
Calls calls2 = (Calls)v2;
|
||||
return new Calls(calls1.mergedLabels | calls2.mergedLabels);
|
||||
}
|
||||
else {
|
||||
return v1;
|
||||
}
|
||||
}
|
||||
else if (v2 instanceof Calls) {
|
||||
return v2;
|
||||
}
|
||||
return super.merge(v1, v2);
|
||||
}
|
||||
|
||||
// ---------- InterpreterExt<Constraint> --------------
|
||||
|
||||
@Override
|
||||
public void init(Constraint previous) {
|
||||
constraint = previous;
|
||||
delta = 0;
|
||||
nullsDelta = 0;
|
||||
|
||||
notNullInsn = -1;
|
||||
notNullCall = 0;
|
||||
notNullNull = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Constraint getAfterData(int insn) {
|
||||
Constraint afterData = mkAfterData();
|
||||
if (notNullInsn == insn) {
|
||||
return new Constraint(afterData.calls | notNullCall, afterData.nulls | notNullNull);
|
||||
}
|
||||
return afterData;
|
||||
}
|
||||
|
||||
private Constraint mkAfterData() {
|
||||
if (delta == 0 && nullsDelta == 0 && notNullInsn == -1) {
|
||||
return constraint;
|
||||
}
|
||||
return new Constraint(constraint.calls | delta, constraint.nulls | nullsDelta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Constraint merge(Constraint data1, Constraint data2) {
|
||||
if (data1.equals(data2)) {
|
||||
return data1;
|
||||
} else {
|
||||
return new Constraint(data1.calls | data2.calls, data1.nulls | data2.nulls);
|
||||
}
|
||||
}
|
||||
}
|
||||
+277
-6
@@ -37,6 +37,7 @@ import static com.intellij.codeInspection.bytecodeAnalysis.AbstractValues.Instan
|
||||
import static com.intellij.codeInspection.bytecodeAnalysis.AbstractValues.ParamValue;
|
||||
import static com.intellij.codeInspection.bytecodeAnalysis.PResults.*;
|
||||
import static org.jetbrains.org.objectweb.asm.Opcodes.*;
|
||||
import static com.intellij.codeInspection.bytecodeAnalysis.Direction.*;
|
||||
|
||||
abstract class PResults {
|
||||
// SoP = sum of products
|
||||
@@ -107,6 +108,18 @@ abstract class PResults {
|
||||
}
|
||||
}
|
||||
|
||||
static PResult combineNullable(PResult r1, PResult r2) throws AnalyzerException {
|
||||
if (Identity == r1) return r2;
|
||||
if (Identity == r2) return r1;
|
||||
if (Return == r1) return r2;
|
||||
if (Return == r2) return r1;
|
||||
if (NPE == r1) return NPE;
|
||||
if (NPE == r2) return NPE;
|
||||
ConditionalNPE cnpe1 = (ConditionalNPE) r1;
|
||||
ConditionalNPE cnpe2 = (ConditionalNPE) r2;
|
||||
return new ConditionalNPE(join(cnpe1.sop, cnpe2.sop));
|
||||
}
|
||||
|
||||
static PResult join(PResult r1, PResult r2) throws AnalyzerException {
|
||||
if (Identity == r1) return r2;
|
||||
if (Identity == r2) return r1;
|
||||
@@ -169,9 +182,12 @@ class NonNullInAnalysis extends Analysis<PResult> {
|
||||
|
||||
final private PendingAction[] pending = ourPending.get();
|
||||
|
||||
private final NonNullInInterpreter interpreter = new NonNullInInterpreter();
|
||||
private final NotNullInterpreter interpreter = new NotNullInterpreter();
|
||||
private PResult[] results;
|
||||
|
||||
// Flag saying that at some branch NPE was found. Used later as an evidence that this param is *NOT* @Nullable (optimization).
|
||||
boolean possibleNPE;
|
||||
|
||||
protected NonNullInAnalysis(RichControlFlow richControlFlow, Direction direction, boolean stable) {
|
||||
super(richControlFlow, direction, stable);
|
||||
results = ourResults.get();
|
||||
@@ -289,6 +305,7 @@ class NonNullInAnalysis extends Analysis<PResult> {
|
||||
|
||||
if (subResult == NPE) {
|
||||
results[stateIndex] = NPE;
|
||||
possibleNPE = true;
|
||||
addComputed(insnIndex, state);
|
||||
return;
|
||||
}
|
||||
@@ -314,6 +331,7 @@ class NonNullInAnalysis extends Analysis<PResult> {
|
||||
if (opcode == ATHROW) {
|
||||
if (taken) {
|
||||
results[stateIndex] = NPE;
|
||||
possibleNPE = true;
|
||||
} else {
|
||||
results[stateIndex] = Identity;
|
||||
}
|
||||
@@ -399,13 +417,216 @@ class NonNullInAnalysis extends Analysis<PResult> {
|
||||
}
|
||||
}
|
||||
|
||||
class NonNullInInterpreter extends BasicInterpreter {
|
||||
class NullableInAnalysis extends Analysis<PResult> {
|
||||
final private State[] pending = ourPendingStates.get();
|
||||
|
||||
private final NullableInterpreter interpreter = new NullableInterpreter();
|
||||
|
||||
protected NullableInAnalysis(RichControlFlow richControlFlow, Direction direction, boolean stable) {
|
||||
super(richControlFlow, direction, stable);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
Equation<Key, Value> mkEquation(PResult result) {
|
||||
if (NPE == result) {
|
||||
return new Equation<Key, Value>(aKey, new Final<Key, Value>(Value.Top));
|
||||
}
|
||||
if (Identity == result || Return == result) {
|
||||
return new Equation<Key, Value>(aKey, new Final<Key, Value>(Value.Null));
|
||||
}
|
||||
else {
|
||||
ConditionalNPE condNpe = (ConditionalNPE) result;
|
||||
Set<Product<Key, Value>> components = new HashSet<Product<Key, Value>>();
|
||||
for (Set<Key> prod : condNpe.sop) {
|
||||
components.add(new Product<Key, Value>(Value.Top, prod));
|
||||
}
|
||||
return new Equation<Key, Value>(aKey, new Pending<Key, Value>(components));
|
||||
}
|
||||
}
|
||||
|
||||
private int id = 0;
|
||||
private Frame<BasicValue> nextFrame = null;
|
||||
private PResult myResult = Identity;
|
||||
private PResult subResult = Identity;
|
||||
private boolean top = false;
|
||||
|
||||
@NotNull
|
||||
protected Equation<Key, Value> analyze() throws AnalyzerException {
|
||||
pendingPush(createStartState());
|
||||
int steps = 0;
|
||||
while (pendingTop > 0 && earlyResult == null) {
|
||||
steps ++;
|
||||
if (steps >= STEPS_LIMIT) {
|
||||
throw new AnalyzerException(null, "limit is reached, steps: " + steps + " in method " + method);
|
||||
}
|
||||
State state = pending[--pendingTop];
|
||||
int insnIndex = state.conf.insnIndex;
|
||||
Conf conf = state.conf;
|
||||
List<Conf> history = state.history;
|
||||
|
||||
boolean fold = false;
|
||||
if (dfsTree.loopEnters[insnIndex]) {
|
||||
for (Conf prev : history) {
|
||||
if (AbstractValues.isInstance(conf, prev)) {
|
||||
fold = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fold) {
|
||||
addComputed(insnIndex, state);
|
||||
}
|
||||
else {
|
||||
State baseState = null;
|
||||
List<State> thisComputed = computed[insnIndex];
|
||||
if (thisComputed != null) {
|
||||
for (State prevState : thisComputed) {
|
||||
if (stateEquiv(state, prevState)) {
|
||||
baseState = prevState;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (baseState == null) {
|
||||
processState(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (earlyResult != null) {
|
||||
return mkEquation(earlyResult);
|
||||
} else {
|
||||
return mkEquation(myResult);
|
||||
}
|
||||
}
|
||||
|
||||
private void processState(State state) throws AnalyzerException {
|
||||
Conf conf = state.conf;
|
||||
int insnIndex = conf.insnIndex;
|
||||
List<Conf> history = state.history;
|
||||
boolean taken = state.taken;
|
||||
Frame<BasicValue> frame = conf.frame;
|
||||
AbstractInsnNode insnNode = methodNode.instructions.get(insnIndex);
|
||||
List<Conf> nextHistory = dfsTree.loopEnters[insnIndex] ? append(history, conf) : history;
|
||||
|
||||
addComputed(insnIndex, state);
|
||||
execute(frame, insnNode);
|
||||
|
||||
if (subResult == NPE || top) {
|
||||
earlyResult = NPE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (subResult instanceof ConditionalNPE) {
|
||||
myResult = combineNullable(myResult, subResult);
|
||||
}
|
||||
|
||||
int opcode = insnNode.getOpcode();
|
||||
switch (opcode) {
|
||||
case ARETURN:
|
||||
if (popValue(frame) instanceof ParamValue) {
|
||||
earlyResult = NPE;
|
||||
}
|
||||
return;
|
||||
case IRETURN:
|
||||
case LRETURN:
|
||||
case FRETURN:
|
||||
case DRETURN:
|
||||
case RETURN:
|
||||
return;
|
||||
default:
|
||||
}
|
||||
|
||||
if (opcode == ATHROW) {
|
||||
if (taken) {
|
||||
earlyResult = NPE;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (opcode == IFNONNULL && popValue(frame) instanceof ParamValue) {
|
||||
int nextInsnIndex = insnIndex + 1;
|
||||
pendingPush(new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, false));
|
||||
return;
|
||||
}
|
||||
|
||||
if (opcode == IFNULL && popValue(frame) instanceof ParamValue) {
|
||||
int nextInsnIndex = methodNode.instructions.indexOf(((JumpInsnNode)insnNode).label);
|
||||
pendingPush(new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, false));
|
||||
return;
|
||||
}
|
||||
|
||||
if (opcode == IFEQ && popValue(frame) == InstanceOfCheckValue) {
|
||||
int nextInsnIndex = methodNode.instructions.indexOf(((JumpInsnNode)insnNode).label);
|
||||
pendingPush(new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, false));
|
||||
return;
|
||||
}
|
||||
|
||||
if (opcode == IFNE && popValue(frame) == InstanceOfCheckValue) {
|
||||
int nextInsnIndex = insnIndex + 1;
|
||||
pendingPush(new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, false));
|
||||
return;
|
||||
}
|
||||
|
||||
// general case
|
||||
for (int nextInsnIndex : controlFlow.transitions[insnIndex]) {
|
||||
Frame<BasicValue> nextFrame1 = nextFrame;
|
||||
if (controlFlow.errors[nextInsnIndex] && controlFlow.errorTransitions.contains(new Edge(insnIndex, nextInsnIndex))) {
|
||||
nextFrame1 = new Frame<BasicValue>(frame);
|
||||
nextFrame1.clearStack();
|
||||
nextFrame1.push(ASMUtils.THROWABLE_VALUE);
|
||||
}
|
||||
pendingPush(new State(++id, new Conf(nextInsnIndex, nextFrame1), nextHistory, taken, false));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private int pendingTop = 0;
|
||||
|
||||
private void pendingPush(State state) throws AnalyzerException {
|
||||
if (pendingTop >= STEPS_LIMIT) {
|
||||
throw new AnalyzerException(null, "limit is reached in method " + method);
|
||||
}
|
||||
pending[pendingTop++] = state;
|
||||
}
|
||||
|
||||
private void execute(Frame<BasicValue> frame, AbstractInsnNode insnNode) throws AnalyzerException {
|
||||
switch (insnNode.getType()) {
|
||||
case AbstractInsnNode.LABEL:
|
||||
case AbstractInsnNode.LINE:
|
||||
case AbstractInsnNode.FRAME:
|
||||
nextFrame = frame;
|
||||
subResult = Identity;
|
||||
top = false;
|
||||
break;
|
||||
default:
|
||||
nextFrame = new Frame<BasicValue>(frame);
|
||||
interpreter.reset();
|
||||
nextFrame.execute(insnNode, interpreter);
|
||||
subResult = interpreter.getSubResult();
|
||||
top = interpreter.top;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class NullityInterpreter extends BasicInterpreter {
|
||||
boolean top = false;
|
||||
final boolean nullableAnalysis;
|
||||
final int nullityMask;
|
||||
private PResult subResult = Identity;
|
||||
|
||||
NullityInterpreter(boolean nullableAnalysis, int nullityMask) {
|
||||
this.nullableAnalysis = nullableAnalysis;
|
||||
this.nullityMask = nullityMask;
|
||||
}
|
||||
|
||||
abstract PResult combine(PResult res1, PResult res2) throws AnalyzerException;
|
||||
|
||||
public PResult getSubResult() {
|
||||
return subResult;
|
||||
}
|
||||
void reset() {
|
||||
subResult = Identity;
|
||||
top = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -445,10 +666,17 @@ class NonNullInInterpreter extends BasicInterpreter {
|
||||
case BALOAD:
|
||||
case CALOAD:
|
||||
case SALOAD:
|
||||
if (value1 instanceof ParamValue) {
|
||||
subResult = NPE;
|
||||
}
|
||||
break;
|
||||
case PUTFIELD:
|
||||
if (value1 instanceof ParamValue) {
|
||||
subResult = NPE;
|
||||
}
|
||||
if (nullableAnalysis && value2 instanceof ParamValue) {
|
||||
subResult = NPE;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
@@ -462,16 +690,24 @@ class NonNullInInterpreter extends BasicInterpreter {
|
||||
case LASTORE:
|
||||
case FASTORE:
|
||||
case DASTORE:
|
||||
case AASTORE:
|
||||
case BASTORE:
|
||||
case CASTORE:
|
||||
case SASTORE:
|
||||
if (value1 instanceof ParamValue) {
|
||||
subResult = NPE;
|
||||
}
|
||||
break;
|
||||
case AASTORE:
|
||||
if (value1 instanceof ParamValue) {
|
||||
subResult = NPE;
|
||||
}
|
||||
if (nullableAnalysis && value3 instanceof ParamValue) {
|
||||
subResult = NPE;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return super.ternaryOperation(insn, value1, value2, value3);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -483,19 +719,54 @@ class NonNullInInterpreter extends BasicInterpreter {
|
||||
subResult = NPE;
|
||||
}
|
||||
switch (opcode) {
|
||||
case INVOKEINTERFACE:
|
||||
if (nullableAnalysis) {
|
||||
for (int i = shift; i < values.size(); i++) {
|
||||
if (values.get(i) instanceof ParamValue) {
|
||||
top = true;
|
||||
return super.naryOperation(insn, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case INVOKESTATIC:
|
||||
case INVOKESPECIAL:
|
||||
case INVOKEVIRTUAL:
|
||||
boolean stable = opcode == INVOKESTATIC || opcode == INVOKESPECIAL;
|
||||
MethodInsnNode methodNode = (MethodInsnNode) insn;
|
||||
Method method = new Method(methodNode.owner, methodNode.name, methodNode.desc);
|
||||
for (int i = shift; i < values.size(); i++) {
|
||||
if (values.get(i) instanceof ParamValue) {
|
||||
Method method = new Method(methodNode.owner, methodNode.name, methodNode.desc);
|
||||
subResult = meet(subResult, new ConditionalNPE(new Key(method, new In(i - shift), stable)));
|
||||
subResult = combine(subResult, new ConditionalNPE(new Key(method, new In(i - shift, nullityMask), stable)));
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return super.naryOperation(insn, values);
|
||||
}
|
||||
}
|
||||
|
||||
class NotNullInterpreter extends NullityInterpreter {
|
||||
|
||||
NotNullInterpreter() {
|
||||
super(false, In.NOT_NULL);
|
||||
}
|
||||
|
||||
@Override
|
||||
PResult combine(PResult res1, PResult res2) throws AnalyzerException {
|
||||
return meet(res1, res2);
|
||||
}
|
||||
}
|
||||
|
||||
class NullableInterpreter extends NullityInterpreter {
|
||||
|
||||
NullableInterpreter() {
|
||||
super(true, In.NULLABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
PResult combine(PResult res1, PResult res2) throws AnalyzerException {
|
||||
return join(res1, res2);
|
||||
}
|
||||
}
|
||||
|
||||
+154
-55
@@ -23,6 +23,7 @@ import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.ModificationTracker;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.search.ProjectScope;
|
||||
@@ -37,10 +38,9 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import static com.intellij.codeInspection.bytecodeAnalysis.Direction.*;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
@@ -48,8 +48,10 @@ import java.util.List;
|
||||
public class ProjectBytecodeAnalysis {
|
||||
public static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.bytecodeAnalysis");
|
||||
public static final Key<Boolean> INFERRED_ANNOTATION = Key.create("INFERRED_ANNOTATION");
|
||||
public static final String NULLABLE_METHOD_TRANSITIVITY = "java.annotations.inference.nullable.method.transitivity";
|
||||
public static final int EQUATIONS_LIMIT = 1000;
|
||||
private final Project myProject;
|
||||
private final boolean nullableMethodTransitivity;
|
||||
|
||||
public static ProjectBytecodeAnalysis getInstance(@NotNull Project project) {
|
||||
return ServiceManager.getService(project, ProjectBytecodeAnalysis.class);
|
||||
@@ -57,6 +59,7 @@ public class ProjectBytecodeAnalysis {
|
||||
|
||||
public ProjectBytecodeAnalysis(Project project) {
|
||||
myProject = project;
|
||||
nullableMethodTransitivity = Registry.is(NULLABLE_METHOD_TRANSITIVITY);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -64,7 +67,7 @@ public class ProjectBytecodeAnalysis {
|
||||
if (!(listOwner instanceof PsiCompiledElement)) {
|
||||
return null;
|
||||
}
|
||||
if (annotationFQN.equals(AnnotationUtil.NOT_NULL) || annotationFQN.equals(ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT)) {
|
||||
if (annotationFQN.equals(AnnotationUtil.NOT_NULL) || annotationFQN.equals(AnnotationUtil.NULLABLE) || annotationFQN.equals(ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT)) {
|
||||
PsiAnnotation[] annotations = findInferredAnnotations(listOwner);
|
||||
for (PsiAnnotation annotation : annotations) {
|
||||
if (annotationFQN.equals(annotation.getQualifiedName())) {
|
||||
@@ -96,34 +99,57 @@ public class ProjectBytecodeAnalysis {
|
||||
private PsiAnnotation[] collectInferredAnnotations(PsiModifierListOwner listOwner) {
|
||||
try {
|
||||
MessageDigest md = BytecodeAnalysisConverter.getMessageDigest();
|
||||
HKey ownerKey = getKey(listOwner, md);
|
||||
if (ownerKey == null) {
|
||||
HKey primaryKey = getKey(listOwner, md);
|
||||
if (primaryKey == null) {
|
||||
return PsiAnnotation.EMPTY_ARRAY;
|
||||
}
|
||||
ArrayList<HKey> allKeys = contractKeys(listOwner, ownerKey);
|
||||
Annotations annotations = loadAnnotations(listOwner, ownerKey, allKeys);
|
||||
boolean notNull = annotations.notNulls.contains(ownerKey);
|
||||
String contractValue = annotations.contracts.get(ownerKey);
|
||||
|
||||
if (notNull && contractValue != null) {
|
||||
return new PsiAnnotation[]{
|
||||
getNotNullAnnotation(),
|
||||
createAnnotationFromText("@" + ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT + "(" + contractValue + ")")
|
||||
};
|
||||
}
|
||||
else if (notNull) {
|
||||
return new PsiAnnotation[]{
|
||||
getNotNullAnnotation()
|
||||
};
|
||||
}
|
||||
else if (contractValue != null) {
|
||||
return new PsiAnnotation[]{
|
||||
createAnnotationFromText("@" + ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT + "(" + contractValue + ")")
|
||||
};
|
||||
}
|
||||
else {
|
||||
return PsiAnnotation.EMPTY_ARRAY;
|
||||
if (listOwner instanceof PsiMethod) {
|
||||
ArrayList<HKey> allKeys = contractKeys((PsiMethod)listOwner, primaryKey);
|
||||
MethodAnnotations methodAnnotations = loadMethodAnnotations((PsiMethod)listOwner, primaryKey, allKeys);
|
||||
boolean notNull = methodAnnotations.notNulls.contains(primaryKey);
|
||||
boolean nullable = methodAnnotations.nullables.contains(primaryKey);
|
||||
String contractValue = methodAnnotations.contracts.get(primaryKey);
|
||||
if (notNull && contractValue != null) {
|
||||
return new PsiAnnotation[]{
|
||||
getNotNullAnnotation(),
|
||||
createAnnotationFromText("@" + ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT + "(" + contractValue + ")")
|
||||
};
|
||||
}
|
||||
if (nullable && contractValue != null) {
|
||||
return new PsiAnnotation[]{
|
||||
getNullableAnnotation(),
|
||||
createAnnotationFromText("@" + ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT + "(" + contractValue + ")")
|
||||
};
|
||||
}
|
||||
else if (notNull) {
|
||||
return new PsiAnnotation[]{
|
||||
getNotNullAnnotation()
|
||||
};
|
||||
}
|
||||
else if (nullable) {
|
||||
return new PsiAnnotation[]{
|
||||
getNullableAnnotation()
|
||||
};
|
||||
}
|
||||
else if (contractValue != null) {
|
||||
return new PsiAnnotation[]{
|
||||
createAnnotationFromText("@" + ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT + "(" + contractValue + ")")
|
||||
};
|
||||
}
|
||||
} else if (listOwner instanceof PsiParameter) {
|
||||
ParameterAnnotations parameterAnnotations = loadParameterAnnotations(primaryKey);
|
||||
if (parameterAnnotations.notNull) {
|
||||
return new PsiAnnotation[]{
|
||||
getNotNullAnnotation()
|
||||
};
|
||||
}
|
||||
else if (parameterAnnotations.nullable) {
|
||||
return new PsiAnnotation[]{
|
||||
getNullableAnnotation()
|
||||
};
|
||||
}
|
||||
}
|
||||
return PsiAnnotation.EMPTY_ARRAY;
|
||||
}
|
||||
catch (EquationsLimitException e) {
|
||||
String externalName = PsiFormatUtil.getExternalName(listOwner, false, Integer.MAX_VALUE);
|
||||
@@ -146,6 +172,16 @@ public class ProjectBytecodeAnalysis {
|
||||
});
|
||||
}
|
||||
|
||||
private PsiAnnotation getNullableAnnotation() {
|
||||
return CachedValuesManager.getManager(myProject).getCachedValue(myProject, new CachedValueProvider<PsiAnnotation>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public Result<PsiAnnotation> compute() {
|
||||
return Result.create(createAnnotationFromText("@" + AnnotationUtil.NULLABLE), ModificationTracker.NEVER_CHANGED);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public PsiAnnotation createContractAnnotation(String contractValue) {
|
||||
return createAnnotationFromText("@org.jetbrains.annotations.Contract(" + contractValue + ")");
|
||||
}
|
||||
@@ -154,7 +190,7 @@ public class ProjectBytecodeAnalysis {
|
||||
public static HKey getKey(@NotNull PsiModifierListOwner owner, MessageDigest md) {
|
||||
LOG.assertTrue(owner instanceof PsiCompiledElement, owner);
|
||||
if (owner instanceof PsiMethod) {
|
||||
return BytecodeAnalysisConverter.psiKey((PsiMethod)owner, new Out(), md);
|
||||
return BytecodeAnalysisConverter.psiKey((PsiMethod)owner, Out, md);
|
||||
}
|
||||
if (owner instanceof PsiParameter) {
|
||||
PsiElement parent = owner.getParent();
|
||||
@@ -162,43 +198,69 @@ public class ProjectBytecodeAnalysis {
|
||||
PsiElement gParent = parent.getParent();
|
||||
if (gParent instanceof PsiMethod) {
|
||||
final int index = ((PsiParameterList)parent).getParameterIndex((PsiParameter)owner);
|
||||
return BytecodeAnalysisConverter.psiKey((PsiMethod)gParent, new In(index), md);
|
||||
return BytecodeAnalysisConverter.psiKey((PsiMethod)gParent, new In(index, In.NOT_NULL), md);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ArrayList<HKey> contractKeys(@NotNull PsiModifierListOwner owner, HKey primaryKey) {
|
||||
if (owner instanceof PsiMethod) {
|
||||
ArrayList<HKey> result = BytecodeAnalysisConverter.mkInOutKeys((PsiMethod)owner, primaryKey);
|
||||
result.add(primaryKey);
|
||||
return result;
|
||||
}
|
||||
ArrayList<HKey> result = new ArrayList<HKey>(1);
|
||||
public static ArrayList<HKey> contractKeys(@NotNull PsiMethod owner, HKey primaryKey) {
|
||||
ArrayList<HKey> result = BytecodeAnalysisConverter.mkInOutKeys(owner, primaryKey);
|
||||
result.add(primaryKey);
|
||||
return result;
|
||||
}
|
||||
|
||||
private Annotations loadAnnotations(@NotNull PsiModifierListOwner owner, @NotNull HKey key, ArrayList<HKey> allKeys)
|
||||
private ParameterAnnotations loadParameterAnnotations(@NotNull HKey notNullKey)
|
||||
throws EquationsLimitException {
|
||||
Annotations result = new Annotations();
|
||||
if (owner instanceof PsiParameter) {
|
||||
final Solver solver = new Solver(new ELattice<Value>(Value.NotNull, Value.Top));
|
||||
collectEquations(allKeys, solver);
|
||||
HashMap<HKey, Value> solutions = solver.solve();
|
||||
BytecodeAnalysisConverter.addParameterAnnotations(solutions, result);
|
||||
} else if (owner instanceof PsiMethod) {
|
||||
final Solver solver = new Solver(new ELattice<Value>(Value.Bot, Value.Top));
|
||||
collectEquations(allKeys, solver);
|
||||
HashMap<HKey, Value> solutions = solver.solve();
|
||||
int arity = ((PsiMethod)owner).getParameterList().getParameters().length;
|
||||
BytecodeAnalysisConverter.addMethodAnnotations(solutions, result, key, arity);
|
||||
|
||||
Map<Bytes, List<HEquations>> equationsCache = new HashMap<Bytes, List<HEquations>>();
|
||||
|
||||
final Solver notNullSolver = new Solver(new ELattice<Value>(Value.NotNull, Value.Top), Value.Top);
|
||||
collectEquations(Collections.singletonList(notNullKey), notNullSolver, equationsCache);
|
||||
|
||||
HashMap<HKey, Value> notNullSolutions = notNullSolver.solve();
|
||||
boolean notNull =
|
||||
(Value.NotNull == notNullSolutions.get(notNullKey)) || (Value.NotNull == notNullSolutions.get(notNullKey.mkUnstable()));
|
||||
|
||||
final Solver nullableSolver = new Solver(new ELattice<Value>(Value.Null, Value.Top), Value.Top);
|
||||
final HKey nullableKey = new HKey(notNullKey.key, notNullKey.dirKey + 1, true);
|
||||
collectEquations(Collections.singletonList(nullableKey), nullableSolver, equationsCache);
|
||||
HashMap<HKey, Value> nullableSolutions = nullableSolver.solve();
|
||||
boolean nullable =
|
||||
(Value.Null == nullableSolutions.get(nullableKey)) || (Value.Null == nullableSolutions.get(nullableKey.mkUnstable()));
|
||||
return new ParameterAnnotations(notNull, nullable);
|
||||
}
|
||||
|
||||
private MethodAnnotations loadMethodAnnotations(@NotNull PsiMethod owner, @NotNull HKey key, ArrayList<HKey> allKeys)
|
||||
throws EquationsLimitException {
|
||||
MethodAnnotations result = new MethodAnnotations();
|
||||
Map<Bytes, List<HEquations>> equationsCache = new HashMap<Bytes, List<HEquations>>();
|
||||
|
||||
final Solver outSolver = new Solver(new ELattice<Value>(Value.Bot, Value.Top), Value.Top);
|
||||
collectEquations(allKeys, outSolver, equationsCache);
|
||||
HashMap<HKey, Value> solutions = outSolver.solve();
|
||||
int arity = owner.getParameterList().getParameters().length;
|
||||
BytecodeAnalysisConverter.addMethodAnnotations(solutions, result, key, arity);
|
||||
|
||||
final Solver nullableMethodSolver = new Solver(new ELattice<Value>(Value.Bot, Value.Null), Value.Bot);
|
||||
HKey nullableKey = key.updateDirection(BytecodeAnalysisConverter.mkDirectionKey(NullableOut));
|
||||
if (nullableMethodTransitivity) {
|
||||
collectEquations(Collections.singletonList(nullableKey), nullableMethodSolver, equationsCache);
|
||||
}
|
||||
else {
|
||||
collectSingleEquation(nullableKey, nullableMethodSolver, equationsCache);
|
||||
}
|
||||
|
||||
HashMap<HKey, Value> nullableSolutions = nullableMethodSolver.solve();
|
||||
if (nullableSolutions.get(nullableKey) == Value.Null || nullableSolutions.get(nullableKey.negate()) == Value.Null) {
|
||||
result.nullables.add(key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void collectEquations(ArrayList<HKey> keys, Solver solver) throws EquationsLimitException {
|
||||
private void collectEquations(List<HKey> keys, Solver solver, @NotNull Map<Bytes, List<HEquations>> cache) throws EquationsLimitException {
|
||||
|
||||
GlobalSearchScope librariesScope = ProjectScope.getLibrariesScope(myProject);
|
||||
HashSet<HKey> queued = new HashSet<HKey>();
|
||||
Stack<HKey> queue = new Stack<HKey>();
|
||||
@@ -208,7 +270,6 @@ public class ProjectBytecodeAnalysis {
|
||||
queued.add(key);
|
||||
}
|
||||
|
||||
HashMap<Bytes, List<HEquations>> cache = new HashMap<Bytes, List<HEquations>>();
|
||||
FileBasedIndex index = FileBasedIndex.getInstance();
|
||||
|
||||
while (!queue.empty()) {
|
||||
@@ -250,6 +311,32 @@ public class ProjectBytecodeAnalysis {
|
||||
}
|
||||
}
|
||||
|
||||
private void collectSingleEquation(HKey hKey, Solver solver, @NotNull Map<Bytes, List<HEquations>> cache) throws EquationsLimitException {
|
||||
GlobalSearchScope librariesScope = ProjectScope.getLibrariesScope(myProject);
|
||||
|
||||
FileBasedIndex index = FileBasedIndex.getInstance();
|
||||
|
||||
ProgressManager.checkCanceled();
|
||||
Bytes bytes = new Bytes(hKey.key);
|
||||
|
||||
List<HEquations> hEquationss = cache.get(bytes);
|
||||
if (hEquationss == null) {
|
||||
hEquationss = index.getValues(BytecodeAnalysisIndex.NAME, bytes, librariesScope);
|
||||
cache.put(bytes, hEquationss);
|
||||
}
|
||||
|
||||
for (HEquations hEquations : hEquationss) {
|
||||
boolean stable = hEquations.stable;
|
||||
for (DirectionResultPair pair : hEquations.results) {
|
||||
int dirKey = pair.directionKey;
|
||||
if (dirKey == hKey.dirKey) {
|
||||
HResult result = pair.hResult;
|
||||
solver.addEquation(new HEquation(new HKey(bytes.bytes, dirKey, stable), result));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private PsiAnnotation createAnnotationFromText(@NotNull final String text) throws IncorrectOperationException {
|
||||
PsiAnnotation annotation = JavaPsiFacade.getElementFactory(myProject).createAnnotationFromText(text, null);
|
||||
@@ -258,11 +345,23 @@ public class ProjectBytecodeAnalysis {
|
||||
}
|
||||
}
|
||||
|
||||
class Annotations {
|
||||
class MethodAnnotations {
|
||||
// @NotNull keys
|
||||
final HashSet<HKey> notNulls = new HashSet<HKey>();
|
||||
// @Nullable keys
|
||||
final HashSet<HKey> nullables = new HashSet<HKey>();
|
||||
// @Contracts
|
||||
final HashMap<HKey, String> contracts = new HashMap<HKey, String>();
|
||||
}
|
||||
|
||||
class ParameterAnnotations {
|
||||
final boolean notNull;
|
||||
final boolean nullable;
|
||||
|
||||
ParameterAnnotations(boolean notNull, boolean nullable) {
|
||||
this.notNull = notNull;
|
||||
this.nullable = nullable;
|
||||
}
|
||||
}
|
||||
|
||||
class EquationsLimitException extends Exception {}
|
||||
|
||||
+4
-2
@@ -265,9 +265,11 @@ final class Solver {
|
||||
|
||||
private final HResultUtil resultUtil;
|
||||
private final HashMap<CoreHKey, HEquation> equations = new HashMap<CoreHKey, HEquation>();
|
||||
private final Value unstableValue;
|
||||
|
||||
Solver(ELattice<Value> lattice) {
|
||||
Solver(ELattice<Value> lattice, Value unstableValue) {
|
||||
this.lattice = lattice;
|
||||
this.unstableValue = unstableValue;
|
||||
resultUtil = new HResultUtil(lattice);
|
||||
}
|
||||
|
||||
@@ -324,7 +326,7 @@ final class Solver {
|
||||
Value value = solved.get(id);
|
||||
|
||||
HKey[] pIds = id.stable ? new HKey[]{id, id.negate()} : new HKey[]{id.negate(), id};
|
||||
Value[] pVals = id.stable ? new Value[]{value, value} : new Value[]{value, lattice.top};
|
||||
Value[] pVals = id.stable ? new Value[]{value, value} : new Value[]{value, unstableValue};
|
||||
|
||||
for (int i = 0; i < pIds.length; i++) {
|
||||
HKey pId = pIds[i];
|
||||
|
||||
+446
@@ -0,0 +1,446 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.codeInspection.bytecodeAnalysis.asm;
|
||||
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes;
|
||||
import org.jetbrains.org.objectweb.asm.Type;
|
||||
import org.jetbrains.org.objectweb.asm.tree.*;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Extended version of {@link org.jetbrains.org.objectweb.asm.tree.analysis.Analyzer}.
|
||||
* It handles frames <b>and</b> additional data.
|
||||
*
|
||||
* @author lambdamix
|
||||
*/
|
||||
public class AnalyzerExt<V extends Value, Data, MyInterpreter extends Interpreter<V> & InterpreterExt<Data>> implements Opcodes {
|
||||
|
||||
private final MyInterpreter interpreter;
|
||||
|
||||
private int n;
|
||||
|
||||
private InsnList insns;
|
||||
|
||||
private List<TryCatchBlockNode>[] handlers;
|
||||
|
||||
private Frame<V>[] frames;
|
||||
|
||||
private Subroutine[] subroutines;
|
||||
|
||||
private boolean[] queued;
|
||||
|
||||
private int[] queue;
|
||||
|
||||
private int top;
|
||||
|
||||
public Data[] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
private Data[] data;
|
||||
|
||||
public AnalyzerExt(final MyInterpreter interpreter, Data[] data, Data startData) {
|
||||
this.interpreter = interpreter;
|
||||
this.data = data;
|
||||
if (data.length > 0) {
|
||||
data[0] = startData;
|
||||
}
|
||||
}
|
||||
|
||||
public Frame<V>[] analyze(final String owner, final MethodNode m) throws AnalyzerException {
|
||||
if ((m.access & (ACC_ABSTRACT | ACC_NATIVE)) != 0) {
|
||||
frames = (Frame<V>[]) new Frame<?>[0];
|
||||
return frames;
|
||||
}
|
||||
final V refV = (V) BasicValue.REFERENCE_VALUE;
|
||||
|
||||
n = m.instructions.size();
|
||||
insns = m.instructions;
|
||||
handlers = (List<TryCatchBlockNode>[]) new List<?>[n];
|
||||
frames = (Frame<V>[]) new Frame<?>[n];
|
||||
subroutines = new Subroutine[n];
|
||||
queued = new boolean[n];
|
||||
queue = new int[n];
|
||||
top = 0;
|
||||
|
||||
// computes exception handlers for each instruction
|
||||
for (int i = 0; i < m.tryCatchBlocks.size(); ++i) {
|
||||
TryCatchBlockNode tcb = m.tryCatchBlocks.get(i);
|
||||
int begin = insns.indexOf(tcb.start);
|
||||
int end = insns.indexOf(tcb.end);
|
||||
for (int j = begin; j < end; ++j) {
|
||||
List<TryCatchBlockNode> insnHandlers = handlers[j];
|
||||
if (insnHandlers == null) {
|
||||
insnHandlers = new ArrayList<TryCatchBlockNode>();
|
||||
handlers[j] = insnHandlers;
|
||||
}
|
||||
insnHandlers.add(tcb);
|
||||
}
|
||||
}
|
||||
|
||||
// computes the subroutine for each instruction:
|
||||
Subroutine main = new Subroutine(null, m.maxLocals, null);
|
||||
List<AbstractInsnNode> subroutineCalls = new ArrayList<AbstractInsnNode>();
|
||||
Map<LabelNode, Subroutine> subroutineHeads = new HashMap<LabelNode, Subroutine>();
|
||||
findSubroutine(0, main, subroutineCalls);
|
||||
while (!subroutineCalls.isEmpty()) {
|
||||
JumpInsnNode jsr = (JumpInsnNode) subroutineCalls.remove(0);
|
||||
Subroutine sub = subroutineHeads.get(jsr.label);
|
||||
if (sub == null) {
|
||||
sub = new Subroutine(jsr.label, m.maxLocals, jsr);
|
||||
subroutineHeads.put(jsr.label, sub);
|
||||
findSubroutine(insns.indexOf(jsr.label), sub, subroutineCalls);
|
||||
} else {
|
||||
sub.callers.add(jsr);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < n; ++i) {
|
||||
if (subroutines[i] != null && subroutines[i].start == null) {
|
||||
subroutines[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// initializes the data structures for the control flow analysis
|
||||
Frame<V> current = newFrame(m.maxLocals, m.maxStack);
|
||||
Frame<V> handler = newFrame(m.maxLocals, m.maxStack);
|
||||
current.setReturn(interpreter.newValue(Type.getReturnType(m.desc)));
|
||||
Type[] args = Type.getArgumentTypes(m.desc);
|
||||
int local = 0;
|
||||
if ((m.access & ACC_STATIC) == 0) {
|
||||
Type ctype = Type.getObjectType(owner);
|
||||
current.setLocal(local++, interpreter.newValue(ctype));
|
||||
}
|
||||
for (int i = 0; i < args.length; ++i) {
|
||||
current.setLocal(local++, interpreter.newValue(args[i]));
|
||||
if (args[i].getSize() == 2) {
|
||||
current.setLocal(local++, interpreter.newValue(null));
|
||||
}
|
||||
}
|
||||
while (local < m.maxLocals) {
|
||||
current.setLocal(local++, interpreter.newValue(null));
|
||||
}
|
||||
|
||||
interpreter.init(data[0]);
|
||||
merge(0, current, null);
|
||||
|
||||
init(owner, m);
|
||||
|
||||
// control flow analysis
|
||||
while (top > 0) {
|
||||
int insn = queue[--top];
|
||||
Frame<V> f = frames[insn];
|
||||
Subroutine subroutine = subroutines[insn];
|
||||
queued[insn] = false;
|
||||
|
||||
AbstractInsnNode insnNode = null;
|
||||
try {
|
||||
insnNode = m.instructions.get(insn);
|
||||
int insnOpcode = insnNode.getOpcode();
|
||||
int insnType = insnNode.getType();
|
||||
|
||||
if (insnType == AbstractInsnNode.LABEL
|
||||
|| insnType == AbstractInsnNode.LINE
|
||||
|| insnType == AbstractInsnNode.FRAME) {
|
||||
interpreter.init(data[insn]);
|
||||
merge(insn + 1, f, subroutine);
|
||||
newControlFlowEdge(insn, insn + 1);
|
||||
} else {
|
||||
// delta
|
||||
interpreter.init(data[insn]);
|
||||
current.init(f).execute(insnNode, interpreter);
|
||||
subroutine = subroutine == null ? null : subroutine.copy();
|
||||
|
||||
if (insnNode instanceof JumpInsnNode) {
|
||||
JumpInsnNode j = (JumpInsnNode) insnNode;
|
||||
if (insnOpcode != GOTO && insnOpcode != JSR) {
|
||||
merge(insn + 1, current, subroutine);
|
||||
newControlFlowEdge(insn, insn + 1);
|
||||
}
|
||||
int jump = insns.indexOf(j.label);
|
||||
if (insnOpcode == JSR) {
|
||||
merge(jump, current, new Subroutine(j.label,
|
||||
m.maxLocals, j));
|
||||
} else {
|
||||
merge(jump, current, subroutine);
|
||||
}
|
||||
newControlFlowEdge(insn, jump);
|
||||
} else if (insnNode instanceof LookupSwitchInsnNode) {
|
||||
LookupSwitchInsnNode lsi = (LookupSwitchInsnNode) insnNode;
|
||||
int jump = insns.indexOf(lsi.dflt);
|
||||
merge(jump, current, subroutine);
|
||||
newControlFlowEdge(insn, jump);
|
||||
for (int j = 0; j < lsi.labels.size(); ++j) {
|
||||
LabelNode label = lsi.labels.get(j);
|
||||
jump = insns.indexOf(label);
|
||||
merge(jump, current, subroutine);
|
||||
newControlFlowEdge(insn, jump);
|
||||
}
|
||||
} else if (insnNode instanceof TableSwitchInsnNode) {
|
||||
TableSwitchInsnNode tsi = (TableSwitchInsnNode) insnNode;
|
||||
int jump = insns.indexOf(tsi.dflt);
|
||||
merge(jump, current, subroutine);
|
||||
newControlFlowEdge(insn, jump);
|
||||
for (int j = 0; j < tsi.labels.size(); ++j) {
|
||||
LabelNode label = tsi.labels.get(j);
|
||||
jump = insns.indexOf(label);
|
||||
merge(jump, current, subroutine);
|
||||
newControlFlowEdge(insn, jump);
|
||||
}
|
||||
} else if (insnOpcode == RET) {
|
||||
if (subroutine == null) {
|
||||
throw new AnalyzerException(insnNode,
|
||||
"RET instruction outside of a sub routine");
|
||||
}
|
||||
for (int i = 0; i < subroutine.callers.size(); ++i) {
|
||||
JumpInsnNode caller = subroutine.callers.get(i);
|
||||
int call = insns.indexOf(caller);
|
||||
if (frames[call] != null) {
|
||||
merge(call + 1, frames[call], current,
|
||||
subroutines[call], subroutine.access);
|
||||
newControlFlowEdge(insn, call + 1);
|
||||
}
|
||||
}
|
||||
} else if (insnOpcode != ATHROW && (insnOpcode < IRETURN || insnOpcode > RETURN)) {
|
||||
if (subroutine != null) {
|
||||
if (insnNode instanceof VarInsnNode) {
|
||||
int var = ((VarInsnNode) insnNode).var;
|
||||
subroutine.access[var] = true;
|
||||
if (insnOpcode == LLOAD || insnOpcode == DLOAD
|
||||
|| insnOpcode == LSTORE
|
||||
|| insnOpcode == DSTORE) {
|
||||
subroutine.access[var + 1] = true;
|
||||
}
|
||||
} else if (insnNode instanceof IincInsnNode) {
|
||||
int var = ((IincInsnNode) insnNode).var;
|
||||
subroutine.access[var] = true;
|
||||
}
|
||||
}
|
||||
merge(insn + 1, current, subroutine);
|
||||
newControlFlowEdge(insn, insn + 1);
|
||||
}
|
||||
}
|
||||
|
||||
List<TryCatchBlockNode> insnHandlers = handlers[insn];
|
||||
if (insnHandlers != null) {
|
||||
for (int i = 0; i < insnHandlers.size(); ++i) {
|
||||
TryCatchBlockNode tcb = insnHandlers.get(i);
|
||||
int jump = insns.indexOf(tcb.handler);
|
||||
if (newControlFlowExceptionEdge(insn, tcb)) {
|
||||
handler.init(f);
|
||||
handler.clearStack();
|
||||
handler.push(refV);
|
||||
merge(jump, handler, subroutine);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (AnalyzerException e) {
|
||||
throw new AnalyzerException(e.node, "Error at instruction "
|
||||
+ insn + ": " + e.getMessage(), e);
|
||||
} catch (Exception e) {
|
||||
throw new AnalyzerException(insnNode, "Error at instruction "
|
||||
+ insn + ": " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
private void findSubroutine(int insn, final Subroutine sub,
|
||||
final List<AbstractInsnNode> calls) throws AnalyzerException {
|
||||
while (true) {
|
||||
if (insn < 0 || insn >= n) {
|
||||
throw new AnalyzerException(null,
|
||||
"Execution can fall off end of the code");
|
||||
}
|
||||
if (subroutines[insn] != null) {
|
||||
return;
|
||||
}
|
||||
subroutines[insn] = sub.copy();
|
||||
AbstractInsnNode node = insns.get(insn);
|
||||
|
||||
// calls findSubroutine recursively on normal successors
|
||||
if (node instanceof JumpInsnNode) {
|
||||
if (node.getOpcode() == JSR) {
|
||||
// do not follow a JSR, it leads to another subroutine!
|
||||
calls.add(node);
|
||||
} else {
|
||||
JumpInsnNode jnode = (JumpInsnNode) node;
|
||||
findSubroutine(insns.indexOf(jnode.label), sub, calls);
|
||||
}
|
||||
} else if (node instanceof TableSwitchInsnNode) {
|
||||
TableSwitchInsnNode tsnode = (TableSwitchInsnNode) node;
|
||||
findSubroutine(insns.indexOf(tsnode.dflt), sub, calls);
|
||||
for (int i = tsnode.labels.size() - 1; i >= 0; --i) {
|
||||
LabelNode l = tsnode.labels.get(i);
|
||||
findSubroutine(insns.indexOf(l), sub, calls);
|
||||
}
|
||||
} else if (node instanceof LookupSwitchInsnNode) {
|
||||
LookupSwitchInsnNode lsnode = (LookupSwitchInsnNode) node;
|
||||
findSubroutine(insns.indexOf(lsnode.dflt), sub, calls);
|
||||
for (int i = lsnode.labels.size() - 1; i >= 0; --i) {
|
||||
LabelNode l = lsnode.labels.get(i);
|
||||
findSubroutine(insns.indexOf(l), sub, calls);
|
||||
}
|
||||
}
|
||||
|
||||
// calls findSubroutine recursively on exception handler successors
|
||||
List<TryCatchBlockNode> insnHandlers = handlers[insn];
|
||||
if (insnHandlers != null) {
|
||||
for (int i = 0; i < insnHandlers.size(); ++i) {
|
||||
TryCatchBlockNode tcb = insnHandlers.get(i);
|
||||
findSubroutine(insns.indexOf(tcb.handler), sub, calls);
|
||||
}
|
||||
}
|
||||
|
||||
// if insn does not falls through to the next instruction, return.
|
||||
switch (node.getOpcode()) {
|
||||
case GOTO:
|
||||
case RET:
|
||||
case TABLESWITCH:
|
||||
case LOOKUPSWITCH:
|
||||
case IRETURN:
|
||||
case LRETURN:
|
||||
case FRETURN:
|
||||
case DRETURN:
|
||||
case ARETURN:
|
||||
case RETURN:
|
||||
case ATHROW:
|
||||
return;
|
||||
}
|
||||
insn++;
|
||||
}
|
||||
}
|
||||
|
||||
public Frame<V>[] getFrames() {
|
||||
return frames;
|
||||
}
|
||||
|
||||
public List<TryCatchBlockNode> getHandlers(final int insn) {
|
||||
return handlers[insn];
|
||||
}
|
||||
|
||||
protected void init(String owner, MethodNode m) throws AnalyzerException {
|
||||
}
|
||||
|
||||
protected Frame<V> newFrame(final int nLocals, final int nStack) {
|
||||
return new Frame<V>(nLocals, nStack);
|
||||
}
|
||||
|
||||
protected Frame<V> newFrame(final Frame<? extends V> src) {
|
||||
return new Frame<V>(src);
|
||||
}
|
||||
|
||||
protected void newControlFlowEdge(final int insn, final int successor) {
|
||||
}
|
||||
|
||||
protected boolean newControlFlowExceptionEdge(final int insn,
|
||||
final int successor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean newControlFlowExceptionEdge(final int insn,
|
||||
final TryCatchBlockNode tcb) {
|
||||
return newControlFlowExceptionEdge(insn, insns.indexOf(tcb.handler));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void merge(final int insn, final Frame<V> frame,
|
||||
final Subroutine subroutine) throws AnalyzerException {
|
||||
Frame<V> oldFrame = frames[insn];
|
||||
Subroutine oldSubroutine = subroutines[insn];
|
||||
boolean changes;
|
||||
|
||||
if (oldFrame == null) {
|
||||
frames[insn] = newFrame(frame);
|
||||
changes = true;
|
||||
} else {
|
||||
changes = oldFrame.merge(frame, interpreter);
|
||||
}
|
||||
|
||||
if (oldSubroutine == null) {
|
||||
if (subroutine != null) {
|
||||
subroutines[insn] = subroutine.copy();
|
||||
changes = true;
|
||||
}
|
||||
} else {
|
||||
if (subroutine != null) {
|
||||
changes |= oldSubroutine.merge(subroutine);
|
||||
}
|
||||
}
|
||||
if (changes && !queued[insn]) {
|
||||
queued[insn] = true;
|
||||
queue[top++] = insn;
|
||||
}
|
||||
|
||||
// delta
|
||||
mergeData(insn, interpreter);
|
||||
}
|
||||
|
||||
private void merge(final int insn, final Frame<V> beforeJSR,
|
||||
final Frame<V> afterRET, final Subroutine subroutineBeforeJSR,
|
||||
final boolean[] access) throws AnalyzerException {
|
||||
Frame<V> oldFrame = frames[insn];
|
||||
Subroutine oldSubroutine = subroutines[insn];
|
||||
boolean changes;
|
||||
|
||||
afterRET.merge(beforeJSR, access);
|
||||
|
||||
if (oldFrame == null) {
|
||||
frames[insn] = newFrame(afterRET);
|
||||
changes = true;
|
||||
} else {
|
||||
changes = oldFrame.merge(afterRET, interpreter);
|
||||
}
|
||||
|
||||
if (oldSubroutine != null && subroutineBeforeJSR != null) {
|
||||
changes |= oldSubroutine.merge(subroutineBeforeJSR);
|
||||
}
|
||||
if (changes && !queued[insn]) {
|
||||
queued[insn] = true;
|
||||
queue[top++] = insn;
|
||||
}
|
||||
|
||||
// delta
|
||||
mergeData(insn, interpreter);
|
||||
}
|
||||
|
||||
private void mergeData(int insn, MyInterpreter interpreter) {
|
||||
boolean changes = false;
|
||||
|
||||
Data oldData = data[insn];
|
||||
Data newData = interpreter.getAfterData(insn);
|
||||
|
||||
if (oldData == null) {
|
||||
data[insn] = newData;
|
||||
changes = true;
|
||||
} else if (newData != null) {
|
||||
Data mergedData = interpreter.merge(oldData, newData);
|
||||
data[insn] = mergedData;
|
||||
changes = !oldData.equals(mergedData);
|
||||
}
|
||||
|
||||
if (changes && !queued[insn]) {
|
||||
queued[insn] = true;
|
||||
queue[top++] = insn;
|
||||
}
|
||||
}
|
||||
}
|
||||
-46
@@ -31,52 +31,6 @@ import java.util.Map;
|
||||
* So, the main point here is handling of subroutines (jsr) and try-catch-finally blocks.
|
||||
*/
|
||||
public class FramelessAnalyzer implements Opcodes {
|
||||
static class Subroutine {
|
||||
|
||||
LabelNode start;
|
||||
boolean[] access;
|
||||
List<JumpInsnNode> callers;
|
||||
|
||||
private Subroutine() {
|
||||
}
|
||||
|
||||
Subroutine(@Nullable final LabelNode start, final int maxLocals,
|
||||
@Nullable final JumpInsnNode caller) {
|
||||
this.start = start;
|
||||
this.access = new boolean[maxLocals];
|
||||
this.callers = new ArrayList<JumpInsnNode>();
|
||||
callers.add(caller);
|
||||
}
|
||||
|
||||
public Subroutine copy() {
|
||||
Subroutine result = new Subroutine();
|
||||
result.start = start;
|
||||
result.access = new boolean[access.length];
|
||||
System.arraycopy(access, 0, result.access, 0, access.length);
|
||||
result.callers = new ArrayList<JumpInsnNode>(callers);
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean merge(final Subroutine subroutine) throws AnalyzerException {
|
||||
boolean changes = false;
|
||||
for (int i = 0; i < access.length; ++i) {
|
||||
if (subroutine.access[i] && !access[i]) {
|
||||
access[i] = true;
|
||||
changes = true;
|
||||
}
|
||||
}
|
||||
if (subroutine.start == start) {
|
||||
for (int i = 0; i < subroutine.callers.size(); ++i) {
|
||||
JumpInsnNode caller = subroutine.callers.get(i);
|
||||
if (!callers.contains(caller)) {
|
||||
callers.add(caller);
|
||||
changes = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
private int n;
|
||||
private InsnList insns;
|
||||
private List<TryCatchBlockNode>[] handlers;
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.codeInspection.bytecodeAnalysis.asm;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
public interface InterpreterExt<Data> { // self: Interpreter[_] =>
|
||||
|
||||
// init interpreter state by passing entry data
|
||||
void init(Data previous);
|
||||
|
||||
// exit data after execution for edge to insn
|
||||
// there are may be different outcomes for different edges if an instruction was branching one
|
||||
Data getAfterData(int insn);
|
||||
|
||||
// merge two states
|
||||
Data merge(Data data1, Data data2);
|
||||
}
|
||||
+52
-13
@@ -31,10 +31,12 @@ import static org.jetbrains.org.objectweb.asm.Opcodes.*;
|
||||
public class LeakingParameters {
|
||||
public final Frame<Value>[] frames;
|
||||
public final boolean[] parameters;
|
||||
public final boolean[] nullableParameters;
|
||||
|
||||
public LeakingParameters(Frame<Value>[] frames, boolean[] parameters) {
|
||||
public LeakingParameters(Frame<Value>[] frames, boolean[] parameters, boolean[] nullableParameters) {
|
||||
this.frames = frames;
|
||||
this.parameters = parameters;
|
||||
this.nullableParameters = nullableParameters;
|
||||
}
|
||||
|
||||
public static LeakingParameters build(String className, MethodNode methodNode, boolean jsr) throws AnalyzerException {
|
||||
@@ -57,7 +59,12 @@ public class LeakingParameters {
|
||||
}
|
||||
}
|
||||
}
|
||||
return new LeakingParameters((Frame<Value>[])(Frame<?>[])frames, collector.leaking);
|
||||
boolean[] notNullParameters = collector.leaking;
|
||||
boolean[] nullableParameters = collector.nullableLeaking;
|
||||
for (int i = 0; i < nullableParameters.length; i++) {
|
||||
nullableParameters[i] |= notNullParameters[i];
|
||||
}
|
||||
return new LeakingParameters((Frame<Value>[])(Frame<?>[])frames, notNullParameters, nullableParameters);
|
||||
}
|
||||
|
||||
public static LeakingParameters buildFast(String className, MethodNode methodNode, boolean jsr) throws AnalyzerException {
|
||||
@@ -66,11 +73,14 @@ public class LeakingParameters {
|
||||
new Analyzer<IParamsValue>(parametersUsage).analyze(className, methodNode) :
|
||||
new LiteAnalyzer<IParamsValue>(parametersUsage).analyze(className, methodNode);
|
||||
int leakingMask = parametersUsage.leaking;
|
||||
boolean[] result = new boolean[parametersUsage.arity];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = (leakingMask & (1 << i)) != 0;
|
||||
int nullableLeakingMask = parametersUsage.nullableLeaking;
|
||||
boolean[] notNullParameters = new boolean[parametersUsage.arity];
|
||||
boolean[] nullableParameters = new boolean[parametersUsage.arity];
|
||||
for (int i = 0; i < notNullParameters.length; i++) {
|
||||
notNullParameters[i] = (leakingMask & (1 << i)) != 0;
|
||||
nullableParameters[i] = ((leakingMask | nullableLeakingMask) & (1 << i)) != 0;
|
||||
}
|
||||
return new LeakingParameters((Frame<Value>[])frames, result);
|
||||
return new LeakingParameters((Frame<Value>[])frames, notNullParameters, nullableParameters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +262,7 @@ class ParametersUsage extends Interpreter<ParamsValue> {
|
||||
|
||||
@Override
|
||||
public ParamsValue ternaryOperation(AbstractInsnNode insn, ParamsValue value1, ParamsValue value2, ParamsValue value3) {
|
||||
return val1;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -288,6 +298,7 @@ class IParametersUsage extends Interpreter<IParamsValue> {
|
||||
static final IParamsValue val1 = new IParamsValue(0, 1);
|
||||
static final IParamsValue val2 = new IParamsValue(0, 2);
|
||||
int leaking = 0;
|
||||
int nullableLeaking = 0;
|
||||
int called = -1;
|
||||
final int rangeStart;
|
||||
final int rangeEnd;
|
||||
@@ -415,8 +426,12 @@ class IParametersUsage extends Interpreter<IParamsValue> {
|
||||
case BALOAD:
|
||||
case CALOAD:
|
||||
case SALOAD:
|
||||
leaking |= value1.params;
|
||||
size = 1;
|
||||
break;
|
||||
case PUTFIELD:
|
||||
leaking |= value1.params;
|
||||
nullableLeaking |= value2.params;
|
||||
size = 1;
|
||||
break;
|
||||
default:
|
||||
@@ -432,15 +447,18 @@ class IParametersUsage extends Interpreter<IParamsValue> {
|
||||
case LASTORE:
|
||||
case FASTORE:
|
||||
case DASTORE:
|
||||
case AASTORE:
|
||||
case BASTORE:
|
||||
case CASTORE:
|
||||
case SASTORE:
|
||||
leaking |= value1.params;
|
||||
break;
|
||||
case AASTORE:
|
||||
leaking |= value1.params;
|
||||
nullableLeaking |= value3.params;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return val1;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -479,9 +497,11 @@ class IParametersUsage extends Interpreter<IParamsValue> {
|
||||
|
||||
class LeakingParametersCollector extends ParametersUsage {
|
||||
final boolean[] leaking;
|
||||
final boolean[] nullableLeaking;
|
||||
LeakingParametersCollector(MethodNode methodNode) {
|
||||
super(methodNode);
|
||||
leaking = new boolean[arity];
|
||||
nullableLeaking = new boolean[arity];
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -518,12 +538,21 @@ class LeakingParametersCollector extends ParametersUsage {
|
||||
case BALOAD:
|
||||
case CALOAD:
|
||||
case SALOAD:
|
||||
case PUTFIELD:
|
||||
boolean[] params = value1.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
leaking[i] |= params[i];
|
||||
}
|
||||
break;
|
||||
case PUTFIELD:
|
||||
params = value1.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
leaking[i] |= params[i];
|
||||
}
|
||||
params = value2.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
nullableLeaking[i] |= params[i];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return super.binaryOperation(insn, value1, value2);
|
||||
@@ -531,23 +560,33 @@ class LeakingParametersCollector extends ParametersUsage {
|
||||
|
||||
@Override
|
||||
public ParamsValue ternaryOperation(AbstractInsnNode insn, ParamsValue value1, ParamsValue value2, ParamsValue value3) {
|
||||
boolean[] params;
|
||||
switch (insn.getOpcode()) {
|
||||
case IASTORE:
|
||||
case LASTORE:
|
||||
case FASTORE:
|
||||
case DASTORE:
|
||||
case AASTORE:
|
||||
case BASTORE:
|
||||
case CASTORE:
|
||||
case SASTORE:
|
||||
boolean[] params = value1.params;
|
||||
params = value1.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
leaking[i] |= params[i];
|
||||
}
|
||||
break;
|
||||
case AASTORE:
|
||||
params = value1.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
leaking[i] |= params[i];
|
||||
}
|
||||
params = value3.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
nullableLeaking[i] |= params[i];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return super.ternaryOperation(insn, value1, value2, value3);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.codeInspection.bytecodeAnalysis.asm;
|
||||
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes;
|
||||
import org.jetbrains.org.objectweb.asm.Type;
|
||||
import org.jetbrains.org.objectweb.asm.tree.*;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Extended version of {@link com.intellij.codeInspection.bytecodeAnalysis.asm.LiteAnalyzer}.
|
||||
* It handles frames <b>and</b> additional data.
|
||||
*
|
||||
* @author lambdamix
|
||||
*/
|
||||
public class LiteAnalyzerExt<V extends Value, Data, MyInterpreter extends Interpreter<V> & InterpreterExt<Data>> implements Opcodes {
|
||||
|
||||
private final MyInterpreter interpreter;
|
||||
private Frame<V>[] frames;
|
||||
private boolean[] queued;
|
||||
private int[] queue;
|
||||
private int top;
|
||||
|
||||
public Data[] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
private Data[] data;
|
||||
|
||||
public LiteAnalyzerExt(final MyInterpreter interpreter, Data[] data, Data startData) {
|
||||
this.interpreter = interpreter;
|
||||
this.data = data;
|
||||
if (data.length > 0) {
|
||||
data[0] = startData;
|
||||
}
|
||||
}
|
||||
|
||||
public Frame<V>[] analyze(final String owner, final MethodNode m) throws AnalyzerException {
|
||||
if ((m.access & (ACC_ABSTRACT | ACC_NATIVE)) != 0) {
|
||||
frames = (Frame<V>[]) new Frame<?>[0];
|
||||
return frames;
|
||||
}
|
||||
|
||||
final V refV = (V) BasicValue.REFERENCE_VALUE;
|
||||
|
||||
int n = m.instructions.size();
|
||||
InsnList insns = m.instructions;
|
||||
List<TryCatchBlockNode>[] handlers = (List<TryCatchBlockNode>[]) new List<?>[n];
|
||||
frames = (Frame<V>[]) new Frame<?>[n];
|
||||
queued = new boolean[n];
|
||||
queue = new int[n];
|
||||
top = 0;
|
||||
|
||||
// computes exception handlers for each instruction
|
||||
for (int i = 0; i < m.tryCatchBlocks.size(); ++i) {
|
||||
TryCatchBlockNode tcb = m.tryCatchBlocks.get(i);
|
||||
int begin = insns.indexOf(tcb.start);
|
||||
int end = insns.indexOf(tcb.end);
|
||||
for (int j = begin; j < end; ++j) {
|
||||
List<TryCatchBlockNode> insnHandlers = handlers[j];
|
||||
if (insnHandlers == null) {
|
||||
insnHandlers = new ArrayList<TryCatchBlockNode>();
|
||||
handlers[j] = insnHandlers;
|
||||
}
|
||||
insnHandlers.add(tcb);
|
||||
}
|
||||
}
|
||||
|
||||
// initializes the data structures for the control flow analysis
|
||||
Frame<V> current = newFrame(m.maxLocals, m.maxStack);
|
||||
Frame<V> handler = newFrame(m.maxLocals, m.maxStack);
|
||||
current.setReturn(interpreter.newValue(Type.getReturnType(m.desc)));
|
||||
Type[] args = Type.getArgumentTypes(m.desc);
|
||||
int local = 0;
|
||||
if ((m.access & ACC_STATIC) == 0) {
|
||||
Type ctype = Type.getObjectType(owner);
|
||||
current.setLocal(local++, interpreter.newValue(ctype));
|
||||
}
|
||||
for (int i = 0; i < args.length; ++i) {
|
||||
current.setLocal(local++, interpreter.newValue(args[i]));
|
||||
if (args[i].getSize() == 2) {
|
||||
current.setLocal(local++, interpreter.newValue(null));
|
||||
}
|
||||
}
|
||||
while (local < m.maxLocals) {
|
||||
current.setLocal(local++, interpreter.newValue(null));
|
||||
}
|
||||
|
||||
interpreter.init(data[0]);
|
||||
merge(0, current);
|
||||
|
||||
// control flow analysis
|
||||
while (top > 0) {
|
||||
int insn = queue[--top];
|
||||
Frame<V> f = frames[insn];
|
||||
queued[insn] = false;
|
||||
|
||||
AbstractInsnNode insnNode = null;
|
||||
try {
|
||||
insnNode = m.instructions.get(insn);
|
||||
int insnOpcode = insnNode.getOpcode();
|
||||
int insnType = insnNode.getType();
|
||||
|
||||
if (insnType == AbstractInsnNode.LABEL || insnType == AbstractInsnNode.LINE || insnType == AbstractInsnNode.FRAME) {
|
||||
interpreter.init(data[insn]);
|
||||
merge(insn + 1, f);
|
||||
} else {
|
||||
// delta
|
||||
interpreter.init(data[insn]);
|
||||
current.init(f).execute(insnNode, interpreter);
|
||||
|
||||
if (insnNode instanceof JumpInsnNode) {
|
||||
JumpInsnNode j = (JumpInsnNode) insnNode;
|
||||
if (insnOpcode != GOTO && insnOpcode != JSR) {
|
||||
merge(insn + 1, current);
|
||||
}
|
||||
int jump = insns.indexOf(j.label);
|
||||
merge(jump, current);
|
||||
} else if (insnNode instanceof LookupSwitchInsnNode) {
|
||||
LookupSwitchInsnNode lsi = (LookupSwitchInsnNode) insnNode;
|
||||
int jump = insns.indexOf(lsi.dflt);
|
||||
merge(jump, current);
|
||||
for (int j = 0; j < lsi.labels.size(); ++j) {
|
||||
LabelNode label = lsi.labels.get(j);
|
||||
jump = insns.indexOf(label);
|
||||
merge(jump, current);
|
||||
}
|
||||
} else if (insnNode instanceof TableSwitchInsnNode) {
|
||||
TableSwitchInsnNode tsi = (TableSwitchInsnNode) insnNode;
|
||||
int jump = insns.indexOf(tsi.dflt);
|
||||
merge(jump, current);
|
||||
for (int j = 0; j < tsi.labels.size(); ++j) {
|
||||
LabelNode label = tsi.labels.get(j);
|
||||
jump = insns.indexOf(label);
|
||||
merge(jump, current);
|
||||
}
|
||||
} else if (insnOpcode != ATHROW
|
||||
&& (insnOpcode < IRETURN || insnOpcode > RETURN)) {
|
||||
merge(insn + 1, current);
|
||||
}
|
||||
}
|
||||
|
||||
List<TryCatchBlockNode> insnHandlers = handlers[insn];
|
||||
if (insnHandlers != null) {
|
||||
for (int i = 0; i < insnHandlers.size(); ++i) {
|
||||
TryCatchBlockNode tcb = insnHandlers.get(i);
|
||||
int jump = insns.indexOf(tcb.handler);
|
||||
handler.init(f);
|
||||
handler.clearStack();
|
||||
handler.push(refV);
|
||||
merge(jump, handler);
|
||||
}
|
||||
}
|
||||
} catch (AnalyzerException e) {
|
||||
throw new AnalyzerException(e.node, "Error at instruction " + insn + ": " + e.getMessage(), e);
|
||||
} catch (Exception e) {
|
||||
throw new AnalyzerException(insnNode, "Error at instruction " + insn + ": " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
public Frame<V>[] getFrames() {
|
||||
return frames;
|
||||
}
|
||||
|
||||
protected Frame<V> newFrame(final int nLocals, final int nStack) {
|
||||
return new Frame<V>(nLocals, nStack);
|
||||
}
|
||||
|
||||
protected Frame<V> newFrame(final Frame<? extends V> src) {
|
||||
return new Frame<V>(src);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void merge(final int insn, final Frame<V> frame) throws AnalyzerException {
|
||||
Frame<V> oldFrame = frames[insn];
|
||||
boolean changes;
|
||||
|
||||
if (oldFrame == null) {
|
||||
frames[insn] = newFrame(frame);
|
||||
changes = true;
|
||||
} else {
|
||||
changes = oldFrame.merge(frame, interpreter);
|
||||
}
|
||||
|
||||
Data oldData = data[insn];
|
||||
Data newData = interpreter.getAfterData(insn);
|
||||
|
||||
if (oldData == null) {
|
||||
data[insn] = newData;
|
||||
changes = true;
|
||||
} else if (newData != null) {
|
||||
Data mergedData = interpreter.merge(oldData, newData);
|
||||
data[insn] = mergedData;
|
||||
changes |= !oldData.equals(mergedData);
|
||||
}
|
||||
|
||||
if (changes && !queued[insn]) {
|
||||
queued[insn] = true;
|
||||
queue[top++] = insn;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -29,11 +29,11 @@ import java.util.List;
|
||||
public class LiteFramelessAnalyzer extends FramelessAnalyzer {
|
||||
|
||||
@Override
|
||||
protected void findSubroutine(int insn, FramelessAnalyzer.Subroutine sub, List<AbstractInsnNode> calls) throws AnalyzerException {
|
||||
protected void findSubroutine(int insn, Subroutine sub, List<AbstractInsnNode> calls) throws AnalyzerException {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void merge(final int insn, final FramelessAnalyzer.Subroutine subroutine) throws AnalyzerException {
|
||||
protected void merge(final int insn, final Subroutine subroutine) throws AnalyzerException {
|
||||
if (!wasQueued[insn]) {
|
||||
wasQueued[insn] = true;
|
||||
if (!queued[insn]) {
|
||||
@@ -44,7 +44,7 @@ public class LiteFramelessAnalyzer extends FramelessAnalyzer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void merge(final int insn, final FramelessAnalyzer.Subroutine subroutineBeforeJSR, final boolean[] access) throws AnalyzerException {
|
||||
protected void merge(final int insn, final Subroutine subroutineBeforeJSR, final boolean[] access) throws AnalyzerException {
|
||||
if (!wasQueued[insn]) {
|
||||
wasQueued[insn] = true;
|
||||
if (!queued[insn]) {
|
||||
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.codeInspection.bytecodeAnalysis.asm;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.org.objectweb.asm.tree.JumpInsnNode;
|
||||
import org.jetbrains.org.objectweb.asm.tree.LabelNode;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
public class Subroutine {
|
||||
|
||||
LabelNode start;
|
||||
boolean[] access;
|
||||
List<JumpInsnNode> callers;
|
||||
|
||||
private Subroutine() {
|
||||
}
|
||||
|
||||
Subroutine(@Nullable final LabelNode start, final int maxLocals,
|
||||
@Nullable final JumpInsnNode caller) {
|
||||
this.start = start;
|
||||
this.access = new boolean[maxLocals];
|
||||
this.callers = new ArrayList<JumpInsnNode>();
|
||||
callers.add(caller);
|
||||
}
|
||||
|
||||
public Subroutine copy() {
|
||||
Subroutine result = new Subroutine();
|
||||
result.start = start;
|
||||
result.access = new boolean[access.length];
|
||||
System.arraycopy(access, 0, result.access, 0, access.length);
|
||||
result.callers = new ArrayList<JumpInsnNode>(callers);
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean merge(final Subroutine subroutine) throws AnalyzerException {
|
||||
boolean changes = false;
|
||||
for (int i = 0; i < access.length; ++i) {
|
||||
if (subroutine.access[i] && !access[i]) {
|
||||
access[i] = true;
|
||||
changes = true;
|
||||
}
|
||||
}
|
||||
if (subroutine.start == start) {
|
||||
for (int i = 0; i < subroutine.callers.size(); ++i) {
|
||||
JumpInsnNode caller = subroutine.callers.get(i);
|
||||
if (!callers.contains(caller)) {
|
||||
callers.add(caller);
|
||||
changes = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
+45
-5
@@ -27,9 +27,11 @@ import com.intellij.psi.util.CachedValuesManager;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.NullableFunction;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.ig.psiutils.SideEffectChecker;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -129,12 +131,13 @@ class ContractInferenceInterpreter {
|
||||
return RecursionManager.doPreventingRecursion(myMethod, true, new Computable<List<MethodContract>>() {
|
||||
@Override
|
||||
public List<MethodContract> compute() {
|
||||
List<MethodContract> delegateContracts = ControlFlowAnalyzer.getMethodContracts(targetMethod);
|
||||
return ContainerUtil.mapNotNull(delegateContracts, new NullableFunction<MethodContract, MethodContract>() {
|
||||
final boolean notNull = NullableNotNullManager.isNotNull(targetMethod);
|
||||
final ValueConstraint[] emptyConstraints = MethodContract.createConstraintArray(myMethod.getParameterList().getParametersCount());
|
||||
List<MethodContract> fromDelegate = ContainerUtil.mapNotNull(ControlFlowAnalyzer.getMethodContracts(targetMethod), new NullableFunction<MethodContract, MethodContract>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public MethodContract fun(MethodContract delegateContract) {
|
||||
ValueConstraint[] answer = MethodContract.createConstraintArray(myMethod.getParameterList().getParametersCount());
|
||||
ValueConstraint[] answer = emptyConstraints;
|
||||
for (int i = 0; i < delegateContract.arguments.length; i++) {
|
||||
if (i >= arguments.length) return null;
|
||||
|
||||
@@ -154,9 +157,17 @@ class ContractInferenceInterpreter {
|
||||
}
|
||||
}
|
||||
}
|
||||
return answer == null ? null : new MethodContract(answer, negated ? negateConstraint(delegateContract.returnValue) : delegateContract.returnValue);
|
||||
ValueConstraint returnValue = negated ? negateConstraint(delegateContract.returnValue) : delegateContract.returnValue;
|
||||
if (notNull && returnValue != THROW_EXCEPTION) {
|
||||
returnValue = NOT_NULL_VALUE;
|
||||
}
|
||||
return answer == null ? null : new MethodContract(answer, returnValue);
|
||||
}
|
||||
});
|
||||
if (notNull) {
|
||||
return ContainerUtil.concat(fromDelegate, Arrays.asList(new MethodContract(emptyConstraints, NOT_NULL_VALUE)));
|
||||
}
|
||||
return fromDelegate;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -213,6 +224,16 @@ class ContractInferenceInterpreter {
|
||||
}
|
||||
}
|
||||
|
||||
if (expr instanceof PsiNewExpression) {
|
||||
return toContracts(states, NOT_NULL_VALUE);
|
||||
}
|
||||
if (expr instanceof PsiMethodCallExpression) {
|
||||
PsiMethod method = ((PsiMethodCallExpression)expr).resolveMethod();
|
||||
if (method != null && NullableNotNullManager.isNotNull(method)) {
|
||||
return toContracts(states, NOT_NULL_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
final ValueConstraint constraint = getLiteralConstraint(expr);
|
||||
if (constraint != null) {
|
||||
return toContracts(states, constraint);
|
||||
@@ -315,7 +336,7 @@ class ContractInferenceInterpreter {
|
||||
private List<MethodContract> visitStatements(List<ValueConstraint[]> states, PsiStatement... statements) {
|
||||
List<MethodContract> result = ContainerUtil.newArrayList();
|
||||
for (PsiStatement statement : statements) {
|
||||
if (statement instanceof PsiBlockStatement && ((PsiBlockStatement)statement).getCodeBlock().getStatements().length == 1) {
|
||||
if (statement instanceof PsiBlockStatement) {
|
||||
result.addAll(visitStatements(states, ((PsiBlockStatement)statement).getCodeBlock().getStatements()));
|
||||
}
|
||||
else if (statement instanceof PsiIfStatement) {
|
||||
@@ -353,12 +374,30 @@ class ContractInferenceInterpreter {
|
||||
List<MethodContract> conditionResults = visitExpression(states, ((PsiAssertStatement)statement).getAssertCondition());
|
||||
result.addAll(toContracts(antecedentsOf(filterReturning(conditionResults, FALSE_VALUE)), THROW_EXCEPTION));
|
||||
}
|
||||
else if (statement instanceof PsiDeclarationStatement && !mayHaveSideEffects((PsiDeclarationStatement)statement)) {
|
||||
continue;
|
||||
}
|
||||
else if (statement instanceof PsiDoWhileStatement) {
|
||||
result.addAll(visitStatements(states, ((PsiDoWhileStatement)statement).getBody()));
|
||||
}
|
||||
|
||||
break; // visit only the first statement unless it's 'if' whose 'then' always returns and the next statement is effectively 'else'
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean mayHaveSideEffects(PsiDeclarationStatement statement) {
|
||||
for (PsiElement element : statement.getDeclaredElements()) {
|
||||
if (element instanceof PsiVariable) {
|
||||
PsiExpression initializer = ((PsiVariable)element).getInitializer();
|
||||
if (initializer != null && SideEffectChecker.mayHaveSideEffects(initializer)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean alwaysReturns(@Nullable PsiStatement statement) {
|
||||
if (statement instanceof PsiReturnStatement || statement instanceof PsiThrowStatement) return true;
|
||||
if (statement instanceof PsiBlockStatement) {
|
||||
@@ -381,6 +420,7 @@ class ContractInferenceInterpreter {
|
||||
if (expr.textMatches(PsiKeyword.TRUE)) return TRUE_VALUE;
|
||||
if (expr.textMatches(PsiKeyword.FALSE)) return FALSE_VALUE;
|
||||
if (expr.textMatches(PsiKeyword.NULL)) return NULL_VALUE;
|
||||
if (((PsiLiteralExpression)expr).getValue() instanceof String) return NOT_NULL_VALUE;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
+11
-5
@@ -591,12 +591,14 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
|
||||
generateBoxingUnboxingInstructionFor(caseExpression, PsiType.INT);
|
||||
final PsiClass psiClass = PsiUtil.resolveClassInType(caseExpression.getType());
|
||||
if (psiClass != null && psiClass.isEnum()) {
|
||||
if (psiClass != null) {
|
||||
addInstruction(new FieldReferenceInstruction(caseExpression, "switch statement expression"));
|
||||
enumValues = new HashSet<PsiEnumConstant>();
|
||||
for (PsiField f : psiClass.getFields()) {
|
||||
if (f instanceof PsiEnumConstant) {
|
||||
enumValues.add((PsiEnumConstant)f);
|
||||
if (psiClass.isEnum()) {
|
||||
enumValues = new HashSet<PsiEnumConstant>();
|
||||
for (PsiField f : psiClass.getFields()) {
|
||||
if (f instanceof PsiEnumConstant) {
|
||||
enumValues.add((PsiEnumConstant)f);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1432,6 +1434,10 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
final PsiAnnotation contractAnno = findContractAnnotation(method);
|
||||
final int paramCount = method.getParameterList().getParametersCount();
|
||||
if (contractAnno != null) {
|
||||
if (AnnotationUtil.isInferredAnnotation(contractAnno) && PsiUtil.canBeOverriden(method)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return CachedValuesManager.getCachedValue(contractAnno, new CachedValueProvider<List<MethodContract>>() {
|
||||
@Nullable
|
||||
@Override
|
||||
|
||||
+3
-1
@@ -70,7 +70,9 @@ public class StandardInstructionVisitor extends InstructionVisitor {
|
||||
|
||||
DfaValueFactory factory = runner.getFactory();
|
||||
if (dfaSource instanceof DfaVariableValue && factory.getVarFactory().getAllQualifiedBy(var).contains(dfaSource)) {
|
||||
dfaSource = factory.createTypeValue(((DfaVariableValue)dfaSource).getVariableType(), ((DfaVariableValue)dfaSource).getInherentNullability());
|
||||
Nullness nullability = memState.isNotNull(dfaSource) ? Nullness.NOT_NULL
|
||||
: ((DfaVariableValue)dfaSource).getInherentNullability();
|
||||
dfaSource = factory.createTypeValue(((DfaVariableValue)dfaSource).getVariableType(), nullability);
|
||||
}
|
||||
|
||||
if (var.getInherentNullability() == Nullness.NOT_NULL) {
|
||||
|
||||
+3
-3
@@ -19,6 +19,7 @@ import com.intellij.codeInspection.dataFlow.DfaPsiUtil;
|
||||
import com.intellij.codeInspection.dataFlow.Nullness;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.openapi.util.Conditions;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.JavaConstantExpressionEvaluator;
|
||||
@@ -50,8 +51,7 @@ public class DfaExpressionFactory {
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOG.error(e);
|
||||
//noinspection unchecked
|
||||
return Condition.FALSE;
|
||||
return Conditions.alwaysFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ public class DfaExpressionFactory {
|
||||
@Nullable
|
||||
private PsiVariable getArrayIndexVariable(@Nullable PsiExpression indexExpression) {
|
||||
Object constant = JavaConstantExpressionEvaluator.computeConstantExpression(indexExpression, false);
|
||||
if (constant instanceof Integer) {
|
||||
if (constant instanceof Integer && ((Integer)constant).intValue() >= 0) {
|
||||
PsiVariable mockVar = myMockIndices.get(constant);
|
||||
if (mockVar == null) {
|
||||
mockVar = JavaPsiFacade.getElementFactory(indexExpression.getProject()).createField("$array$index$" + constant, PsiType.INT);
|
||||
|
||||
+9
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2011 JetBrains s.r.o.
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,10 +15,7 @@
|
||||
*/
|
||||
package com.intellij.codeInspection.equalsAndHashcode;
|
||||
|
||||
import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool;
|
||||
import com.intellij.codeInspection.InspectionsBundle;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.ProblemsHolder;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ProjectRootManager;
|
||||
@@ -36,10 +33,10 @@ import org.jetbrains.annotations.Nullable;
|
||||
/**
|
||||
* @author max
|
||||
*/
|
||||
public class EqualsAndHashcode extends BaseJavaBatchLocalInspectionTool {
|
||||
public class EqualsAndHashcodeBase extends BaseJavaBatchLocalInspectionTool {
|
||||
@Override
|
||||
@NotNull
|
||||
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
|
||||
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
|
||||
final Project project = holder.getProject();
|
||||
Pair<PsiMethod, PsiMethod> pair = CachedValuesManager.getManager(project).getCachedValue(project, new CachedValueProvider<Pair<PsiMethod, PsiMethod>>() {
|
||||
@Override
|
||||
@@ -92,7 +89,7 @@ public class EqualsAndHashcode extends BaseJavaBatchLocalInspectionTool {
|
||||
hasEquals[0]
|
||||
? InspectionsBundle.message("inspection.equals.hashcode.only.one.defined.problem.descriptor", "<code>equals()</code>", "<code>hashCode()</code>")
|
||||
: InspectionsBundle.message("inspection.equals.hashcode.only.one.defined.problem.descriptor","<code>hashCode()</code>", "<code>equals()</code>"),
|
||||
(LocalQuickFix[])null);
|
||||
buildFixes(isOnTheFly, hasEquals[0]));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -130,4 +127,8 @@ public class EqualsAndHashcode extends BaseJavaBatchLocalInspectionTool {
|
||||
public String getShortName() {
|
||||
return "EqualsAndHashcode";
|
||||
}
|
||||
|
||||
protected LocalQuickFix[] buildFixes(boolean isOnTheFly, boolean hasEquals) {
|
||||
return LocalQuickFix.EMPTY_ARRAY;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.codeInspection.inheritance;
|
||||
|
||||
import com.intellij.openapi.extensions.ExtensionPointName;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* @author nik
|
||||
*/
|
||||
public abstract class ImplementedAtRuntimeCondition {
|
||||
public static final ExtensionPointName<ImplementedAtRuntimeCondition> EP_NAME = ExtensionPointName.create("com.intellij.codeInsight.implementedAtRuntime");
|
||||
|
||||
public abstract boolean isImplementedAtRuntime(@NotNull PsiClass psiClass);
|
||||
}
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2013 JetBrains s.r.o.
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -540,7 +540,7 @@ public class JavaDocLocalInspectionBase extends BaseJavaBatchLocalInspectionTool
|
||||
}
|
||||
|
||||
if (required && superMethods.length == 0 && isTagRequired(psiMethod, "@throws") && psiMethod.getThrowsList().getReferencedTypes().length > 0) {
|
||||
final Map<PsiClassType, PsiClass> declaredExceptions = new HashMap<PsiClassType, PsiClass>();
|
||||
final Map<PsiClassType, PsiClass> declaredExceptions = new LinkedHashMap<PsiClassType, PsiClass>();
|
||||
final PsiClassType[] classTypes = psiMethod.getThrowsList().getReferencedTypes();
|
||||
for (PsiClassType classType : classTypes) {
|
||||
final PsiClass psiClass = classType.resolve();
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ public class UnnecessaryModuleDependencyInspection extends GlobalInspectionTool
|
||||
if (refEntity instanceof RefModule){
|
||||
final RefModule refModule = (RefModule)refEntity;
|
||||
final Module module = refModule.getModule();
|
||||
if (module.isDisposed()) return CommonProblemDescriptor.EMPTY_ARRAY;
|
||||
if (module.isDisposed() || !scope.containsModule(module)) return CommonProblemDescriptor.EMPTY_ARRAY;
|
||||
final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
|
||||
final OrderEntry[] declaredDependencies = moduleRootManager.getOrderEntries();
|
||||
final Module[] declaredModuleDependencies = moduleRootManager.getDependencies();
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ public class UnusedLibrariesInspection extends GlobalInspectionTool {
|
||||
if (refEntity instanceof RefModule) {
|
||||
final RefModule refModule = (RefModule)refEntity;
|
||||
final Module module = refModule.getModule();
|
||||
if (module.isDisposed()) return CommonProblemDescriptor.EMPTY_ARRAY;
|
||||
if (module.isDisposed() || !scope.containsModule(module)) return CommonProblemDescriptor.EMPTY_ARRAY;
|
||||
final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
|
||||
final Set<VirtualFile> usedRoots = refModule.getUserData(UnusedLibraryGraphAnnotator.USED_LIBRARY_ROOTS);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2013 JetBrains s.r.o.
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -57,6 +57,9 @@ public abstract class BaseClassesAnalysisAction extends BaseAnalysisAction {
|
||||
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (project.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
if (!upToDate) {
|
||||
final int i = Messages.showYesNoCancelDialog(getProject(), AnalysisScopeBundle.message("recompile.confirmation.message"),
|
||||
AnalysisScopeBundle.message("project.is.out.of.date"), Messages.getWarningIcon());
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2009 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.application.options;
|
||||
|
||||
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
|
||||
import com.intellij.psi.codeStyle.FileTypeIndentOptionsProvider;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.openapi.fileTypes.FileType;
|
||||
import com.intellij.openapi.fileTypes.StdFileTypes;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public class JavaIndentOptionsProvider implements FileTypeIndentOptionsProvider {
|
||||
public CommonCodeStyleSettings.IndentOptions createIndentOptions() {
|
||||
return new CommonCodeStyleSettings.IndentOptions();
|
||||
}
|
||||
|
||||
public FileType getFileType() {
|
||||
return StdFileTypes.JAVA;
|
||||
}
|
||||
|
||||
public IndentOptionsEditor createOptionsEditor() {
|
||||
return new JavaIndentOptionsEditor();
|
||||
}
|
||||
|
||||
@NonNls
|
||||
public String getPreviewText() {
|
||||
return "public class Foo {\n" +
|
||||
" public int[] X = new int[] { 1, 3, 5,\n" +
|
||||
" 7, 9, 11};\n" +
|
||||
" public void foo(boolean a, int x,\n" +
|
||||
" int y, int z) {\n" +
|
||||
" a = x == 0 &&\n" +
|
||||
" (y == 0 ||\n" +
|
||||
" z <= 4) &&\n" +
|
||||
" z >= 0;" +
|
||||
" label1: do {\n" +
|
||||
" try {\n" +
|
||||
" if(x > 0) {\n" +
|
||||
" int someVariable = a ? \n" +
|
||||
" x : \n" +
|
||||
" y;\n" +
|
||||
" } else if (x < 0) {\n" +
|
||||
" int someVariable = (y +\n" +
|
||||
" z\n" +
|
||||
" );\n" +
|
||||
" someVariable = x = \n" +
|
||||
" x +\n" +
|
||||
" y;\n" +
|
||||
" } else {\n" +
|
||||
" label2:\n" +
|
||||
" for (int i = 0;\n" +
|
||||
" i < 5;\n" +
|
||||
" i++) doSomething(i);\n" +
|
||||
" }\n" +
|
||||
" switch(a) {\n" +
|
||||
" case 0: \n" +
|
||||
" doCase0();\n" +
|
||||
" break;\n" +
|
||||
" default: \n" +
|
||||
" doDefault();\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" catch(Exception e) {\n" +
|
||||
" processException(e.getMessage(),\n" +
|
||||
" x + y, z, a);\n" +
|
||||
" }\n" +
|
||||
" finally {\n" +
|
||||
" processFinally();\n" +
|
||||
" }\n" +
|
||||
" }while(true);\n" +
|
||||
"\n" +
|
||||
" if (2 < 3) return;\n" +
|
||||
" if (3 < 4)\n" +
|
||||
" return;\n" +
|
||||
" do x++ while (x < 10000);\n" +
|
||||
" while (x < 50000) x++;\n" +
|
||||
" for (int i = 0; i < 5; i++) System.out.println(i);\n" +
|
||||
" }\n" +
|
||||
" private class InnerClass implements I1,\n" +
|
||||
" I2 {\n" +
|
||||
" public void bar() throws E1,\n" +
|
||||
" E2 {\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
}
|
||||
|
||||
public void prepareForReformat(final PsiFile psiFile) {
|
||||
psiFile.putUserData(PsiUtil.FILE_LANGUAGE_LEVEL_KEY, LanguageLevel.HIGHEST);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.intellij.codeInsight;
|
||||
|
||||
import com.intellij.codeInsight.completion.AllClassesGetter;
|
||||
import com.intellij.codeInsight.completion.CompletionUtil;
|
||||
import com.intellij.codeInsight.completion.JavaCompletionUtil;
|
||||
import com.intellij.codeInsight.completion.PrefixMatcher;
|
||||
import com.intellij.lang.Language;
|
||||
@@ -33,21 +34,21 @@ import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.search.searches.ClassInheritorsSearch;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.psi.util.proximity.PsiProximityComparator;
|
||||
import com.intellij.refactoring.util.RefactoringUtil;
|
||||
import com.intellij.util.Consumer;
|
||||
import com.intellij.util.FilteredQuery;
|
||||
import com.intellij.util.Processor;
|
||||
import com.intellij.util.Query;
|
||||
import com.intellij.psi.util.FileTypeUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class CodeInsightUtil {
|
||||
@Nullable
|
||||
@@ -283,6 +284,11 @@ public class CodeInsightUtil {
|
||||
|
||||
final Processor<PsiClass> inheritorsProcessor =
|
||||
createInheritorsProcessor(context, baseType, arrayDim, getRawSubtypes, consumer, baseClass, baseSubstitutor);
|
||||
|
||||
addContextTypeArguments(context, baseType, inheritorsProcessor);
|
||||
|
||||
if (baseClass.hasModifierProperty(PsiModifier.FINAL)) return;
|
||||
|
||||
if (matcher.getPrefix().length() > 2) {
|
||||
AllClassesGetter.processJavaClasses(matcher, context.getProject(), scope, new Processor<PsiClass>() {
|
||||
@Override
|
||||
@@ -307,6 +313,30 @@ public class CodeInsightUtil {
|
||||
|
||||
}
|
||||
|
||||
private static void addContextTypeArguments(final PsiElement context,
|
||||
final PsiClassType baseType,
|
||||
final Processor<PsiClass> inheritorsProcessor) {
|
||||
ApplicationManager.getApplication().runReadAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Set<String> usedNames = ContainerUtil.newHashSet();
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(context.getProject());
|
||||
PsiElement each = context;
|
||||
while (true) {
|
||||
PsiTypeParameterListOwner typed = PsiTreeUtil.getParentOfType(each, PsiTypeParameterListOwner.class);
|
||||
if (typed == null) break;
|
||||
for (PsiTypeParameter parameter : typed.getTypeParameters()) {
|
||||
if (baseType.isAssignableFrom(factory.createType(parameter)) && usedNames.add(parameter.getName())) {
|
||||
inheritorsProcessor.process(CompletionUtil.getOriginalOrSelf(parameter));
|
||||
}
|
||||
}
|
||||
|
||||
each = typed;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Processor<PsiClass> createInheritorsProcessor(final PsiElement context, final PsiClassType baseType,
|
||||
final int arrayDim,
|
||||
final boolean getRawSubtypes,
|
||||
|
||||
+22
-10
@@ -51,14 +51,14 @@ public class ExternalAnnotationsLineMarkerProvider implements LineMarkerProvider
|
||||
if (!(element instanceof PsiModifierListOwner)) return null;
|
||||
if (element instanceof PsiParameter || element instanceof PsiLocalVariable) return null;
|
||||
|
||||
if (!shouldShowSignature(preferCompiledElement((PsiModifierListOwner)element))) {
|
||||
if (!shouldShowSignature((PsiModifierListOwner)element)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Function<PsiModifierListOwner, String> annotationsCollector = new Function<PsiModifierListOwner, String>() {
|
||||
@Override
|
||||
public String fun(PsiModifierListOwner owner) {
|
||||
return XmlStringUtil.wrapInHtml(JavaDocInfoGenerator.generateSignature(preferCompiledElement(owner)));
|
||||
return XmlStringUtil.wrapInHtml(JavaDocInfoGenerator.generateSignature(owner));
|
||||
}
|
||||
};
|
||||
return new LineMarkerInfo<PsiModifierListOwner>((PsiModifierListOwner)element, element.getTextOffset(), AllIcons.Gutter.ExtAnnotation,
|
||||
@@ -67,11 +67,6 @@ public class ExternalAnnotationsLineMarkerProvider implements LineMarkerProvider
|
||||
GutterIconRenderer.Alignment.LEFT);
|
||||
}
|
||||
|
||||
private static PsiModifierListOwner preferCompiledElement(PsiModifierListOwner element) {
|
||||
PsiElement original = element.getOriginalElement();
|
||||
return original instanceof PsiModifierListOwner ? (PsiModifierListOwner)original : element;
|
||||
}
|
||||
|
||||
private static boolean shouldShowSignature(PsiModifierListOwner owner) {
|
||||
if (hasNonCodeAnnotations(owner)) {
|
||||
return true;
|
||||
@@ -91,10 +86,27 @@ public class ExternalAnnotationsLineMarkerProvider implements LineMarkerProvider
|
||||
private static boolean hasNonCodeAnnotations(@NotNull PsiModifierListOwner element) {
|
||||
Project project = element.getProject();
|
||||
PsiAnnotation[] externalAnnotations = ExternalAnnotationsManager.getInstance(project).findExternalAnnotations(element);
|
||||
if (externalAnnotations != null && externalAnnotations.length > 0) {
|
||||
return true;
|
||||
if (externalAnnotations != null) {
|
||||
for (PsiAnnotation annotation : externalAnnotations) {
|
||||
if (isVisibleAnnotation(annotation)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return InferredAnnotationsManager.getInstance(project).findInferredAnnotations(element).length > 0;
|
||||
for (PsiAnnotation annotation : InferredAnnotationsManager.getInstance(project).findInferredAnnotations(element)) {
|
||||
if (isVisibleAnnotation(annotation)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isVisibleAnnotation(@NotNull PsiAnnotation annotation) {
|
||||
PsiJavaCodeReferenceElement ref = annotation.getNameReferenceElement();
|
||||
if (ref == null) return true;
|
||||
|
||||
PsiElement target = ref.resolve();
|
||||
return !(target instanceof PsiClass) || JavaDocInfoGenerator.isDocumentedAnnotationType(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -484,7 +484,7 @@ public class JavaCompletionData extends JavaAwareCompletionData {
|
||||
}
|
||||
}
|
||||
|
||||
if ((isInsideParameterList(position) || isAtResourceVariableStart(position)) &&
|
||||
if ((isInsideParameterList(position) || isAtResourceVariableStart(position) || isAtCatchVariableStart(position)) &&
|
||||
!psiElement().afterLeaf(PsiKeyword.FINAL).accepts(position) &&
|
||||
!AFTER_DOT.accepts(position)) {
|
||||
result.addElement(TailTypeDecorator.withTail(createKeyword(position, PsiKeyword.FINAL), TailType.HUMBLE_SPACE_BEFORE_WORD));
|
||||
@@ -673,6 +673,10 @@ public class JavaCompletionData extends JavaAwareCompletionData {
|
||||
return psiElement().insideStarting(psiElement(PsiTypeElement.class).withParent(PsiResourceList.class)).accepts(position);
|
||||
}
|
||||
|
||||
private static boolean isAtCatchVariableStart(PsiElement position) {
|
||||
return psiElement().insideStarting(psiElement(PsiTypeElement.class).withParent(PsiCatchSection.class)).accepts(position);
|
||||
}
|
||||
|
||||
private static void addBreakContinue(CompletionResultSet result, PsiElement position) {
|
||||
PsiLoopStatement loop = PsiTreeUtil.getParentOfType(position, PsiLoopStatement.class);
|
||||
|
||||
|
||||
@@ -278,10 +278,12 @@ public class JavaCompletionSorting {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Comparable weigh(@NotNull LookupElement item) {
|
||||
public MyResult weigh(@NotNull LookupElement item) {
|
||||
final Object object = item.getObject();
|
||||
|
||||
if (object instanceof PsiClass) {
|
||||
if (object instanceof PsiTypeParameter) return MyResult.typeParameter;
|
||||
|
||||
if (myTypeParameter != null && object.equals(PsiUtil.resolveClassInType(TypeConversionUtil.typeParameterErasure(myTypeParameter)))) {
|
||||
return MyResult.exactlyExpected;
|
||||
}
|
||||
@@ -340,6 +342,7 @@ public class JavaCompletionSorting {
|
||||
|
||||
private enum MyResult {
|
||||
expectedNoSelect,
|
||||
typeParameter,
|
||||
exactlyDefault,
|
||||
ofDefaultType,
|
||||
exactlyExpected,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user