diff options
author | Gayan Perera | 2021-04-20 17:19:13 +0000 |
---|---|---|
committer | Sarika Sinha | 2021-04-28 17:27:07 +0000 |
commit | 317732222f46150e4575592911af3aa6d7644967 (patch) | |
tree | a18492ce53a812bb5b1bd1276fe90ef58f3403ff | |
parent | b39c8c3703c6d975ab4fecc46c83489ab422a24b (diff) | |
download | eclipse.jdt.debug-I20210428-1800.tar.gz eclipse.jdt.debug-I20210428-1800.tar.xz eclipse.jdt.debug-I20210428-1800.zip |
Bug 572629 - Add support debug hovers on chain of objectsI20210508-1800I20210507-1800I20210506-1800I20210505-1800I20210505-0510I20210505-0100I20210504-1800I20210503-1800I20210502-1800I20210502-0730I20210501-1800I20210430-1800I20210429-1800I20210429-0600I20210428-1800
Extend the current debug hovering for chain objects by using performing
a code evaluation for such expressions. Before this change the hovering
only try to reads variables that are available in current frame and
member variables. All other chained variables which are identified as
qualified names are evaluated using ASTEvaluationEngine, and for
synthetic members such as array.length we try to select the node of the
array field by moving the offset and evaluate it in the same way as
qualified names.
Change-Id: Ida6f14f85b5aa3fcc6019216318a03ba694c19a0
Signed-off-by: Gayan Perera <gayanper@gmail.com>
Reviewed-on: https://git.eclipse.org/r/c/jdt/eclipse.jdt.debug/+/179566
Tested-by: Sarika Sinha <sarika.sinha@in.ibm.com>
Reviewed-by: Sarika Sinha <sarika.sinha@in.ibm.com>
4 files changed, 387 insertions, 24 deletions
diff --git a/org.eclipse.jdt.debug.tests/java8/Bug572629.java b/org.eclipse.jdt.debug.tests/java8/Bug572629.java new file mode 100644 index 000000000..7b97fa906 --- /dev/null +++ b/org.eclipse.jdt.debug.tests/java8/Bug572629.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2021 Gayan Perera and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Gayan Perera - initial API and implementation + *******************************************************************************/ +public class Bug572629 { + private String payload; + + private String[] payloads; + + private static String[] PAYLOADS = new String[] {"1"}; + + public Bug572629(String payload) { + this.payload = payload; + this.payloads = new String[]{payload}; + } + + @Override + public boolean equals(Object o) { + if (o == null || o.getClass() != this.getClass()) { + return false; + } + Bug572629 other = (Bug572629) o; + System.out.print(PAYLOADS.length); + return this.payload == other.payload && this.payloads.length == other.payloads.length; + } + + public static void main(String[] args) { + new Bug572629("p").equals(new Bug572629("r")); + } +}
\ No newline at end of file diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java index 6054221df..56668be2f 100644 --- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java @@ -493,6 +493,7 @@ public abstract class AbstractDebugTest extends TestCase implements IEvaluation cfgs.add(createLaunchConfiguration(jp, "Bug564801")); cfgs.add(createLaunchConfiguration(jp, "Bug567801")); cfgs.add(createLaunchConfiguration(jp, "Bug571230")); + cfgs.add(createLaunchConfiguration(jp, "Bug572629")); loaded18 = true; waitForBuild(); } diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/DebugHoverTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/DebugHoverTests.java index f81274f8e..5f0b7e301 100644 --- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/DebugHoverTests.java +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/DebugHoverTests.java @@ -23,9 +23,11 @@ import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.model.IBreakpoint; import org.eclipse.debug.core.model.ILineBreakpoint; import org.eclipse.debug.core.model.IStackFrame; +import org.eclipse.debug.core.model.IVariable; import org.eclipse.debug.internal.ui.views.console.ProcessConsole; import org.eclipse.jdi.internal.StringReferenceImpl; import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.debug.core.IJavaArray; import org.eclipse.jdt.debug.core.IJavaBreakpoint; import org.eclipse.jdt.debug.core.IJavaStackFrame; import org.eclipse.jdt.debug.core.IJavaThread; @@ -282,6 +284,227 @@ public class DebugHoverTests extends AbstractDebugUiTests { } } + public void testBug572629_ChainFieldHover_2Chains_ExpectValueFromChain() throws Exception { + sync(() -> TestUtil.waitForJobs(getName(), 1000, 10000, ProcessConsole.class)); + + final String typeName = "Bug572629"; + final String expectedMethod = "equals"; + final int frameNumber = 2; + final int bpLine = 33; + + IJavaBreakpoint bp = createLineBreakpoint(bpLine, "", typeName + ".java", typeName); + bp.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD); + IFile file = (IFile) bp.getMarker().getResource(); + assertEquals(typeName + ".java", file.getName()); + + IJavaThread thread = null; + try { + thread = launchToBreakpoint(typeName); + CompilationUnitEditor part = openEditorAndValidateStack(expectedMethod, frameNumber, file, thread); + + JavaDebugHover hover = new JavaDebugHover(); + hover.setEditor(part); + + String variableName = "payload"; + int offset = part.getViewer().getDocument().get().indexOf("other.payload") + "other.".length(); + IRegion region = new Region(offset, "payload".length()); + String text = selectAndReveal(part, bpLine, region); + assertEquals(variableName, text); + IVariable info = (IVariable) sync(() -> hover.getHoverInfo2(part.getViewer(), region)); + + assertNotNull(info); + assertEquals("other.payload", info.getName()); + assertEquals("r", info.getValue().getValueString()); + } finally { + terminateAndRemove(thread); + removeAllBreakpoints(); + } + } + + public void testBug572629_ChainFieldHover_ArrayLengthChainsOnThisExpression_ExpectValueFromChain() throws Exception { + sync(() -> TestUtil.waitForJobs(getName(), 1000, 10000, ProcessConsole.class)); + + final String typeName = "Bug572629"; + final String expectedMethod = "equals"; + final int frameNumber = 2; + final int bpLine = 33; + + IJavaBreakpoint bp = createLineBreakpoint(bpLine, "", typeName + ".java", typeName); + bp.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD); + IFile file = (IFile) bp.getMarker().getResource(); + assertEquals(typeName + ".java", file.getName()); + + IJavaThread thread = null; + try { + thread = launchToBreakpoint(typeName); + CompilationUnitEditor part = openEditorAndValidateStack(expectedMethod, frameNumber, file, thread); + + JavaDebugHover hover = new JavaDebugHover(); + hover.setEditor(part); + + String variableName = "length"; + int offset = part.getViewer().getDocument().get().indexOf("this.payloads.length") + "this.payloads.".length(); + IRegion region = new Region(offset, "length".length()); + String text = selectAndReveal(part, bpLine, region); + assertEquals(variableName, text); + IVariable info = (IVariable) sync(() -> hover.getHoverInfo2(part.getViewer(), region)); + + assertNotNull(info); + assertEquals("this.payloads.length", info.getName()); + assertEquals("1", info.getValue().getValueString()); + } finally { + terminateAndRemove(thread); + removeAllBreakpoints(); + } + } + + public void testBug572629_ChainFieldHover_ArrayLengthChainsOnVariableExpression_ExpectValueFromChain() throws Exception { + sync(() -> TestUtil.waitForJobs(getName(), 1000, 10000, ProcessConsole.class)); + + final String typeName = "Bug572629"; + final String expectedMethod = "equals"; + final int frameNumber = 2; + final int bpLine = 33; + + IJavaBreakpoint bp = createLineBreakpoint(bpLine, "", typeName + ".java", typeName); + bp.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD); + IFile file = (IFile) bp.getMarker().getResource(); + assertEquals(typeName + ".java", file.getName()); + + IJavaThread thread = null; + try { + thread = launchToBreakpoint(typeName); + CompilationUnitEditor part = openEditorAndValidateStack(expectedMethod, frameNumber, file, thread); + + JavaDebugHover hover = new JavaDebugHover(); + hover.setEditor(part); + + String variableName = "length"; + int offset = part.getViewer().getDocument().get().indexOf("other.payloads.length") + "other.payloads.".length(); + IRegion region = new Region(offset, "length".length()); + String text = selectAndReveal(part, bpLine, region); + assertEquals(variableName, text); + IVariable info = (IVariable) sync(() -> hover.getHoverInfo2(part.getViewer(), region)); + + assertNotNull(info); + assertEquals("other.payloads.length", info.getName()); + assertEquals("1", info.getValue().getValueString()); + } finally { + terminateAndRemove(thread); + removeAllBreakpoints(); + } + } + + public void testBug572629_ChainFieldHover_ArrayLengthChainsOnVariableThisExpression_ExpectValueFromChain() throws Exception { + sync(() -> TestUtil.waitForJobs(getName(), 1000, 10000, ProcessConsole.class)); + + final String typeName = "Bug572629"; + final String expectedMethod = "equals"; + final int frameNumber = 2; + final int bpLine = 33; + + IJavaBreakpoint bp = createLineBreakpoint(bpLine, "", typeName + ".java", typeName); + bp.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD); + IFile file = (IFile) bp.getMarker().getResource(); + assertEquals(typeName + ".java", file.getName()); + + IJavaThread thread = null; + try { + thread = launchToBreakpoint(typeName); + CompilationUnitEditor part = openEditorAndValidateStack(expectedMethod, frameNumber, file, thread); + + JavaDebugHover hover = new JavaDebugHover(); + hover.setEditor(part); + + String variableName = "length"; + int offset = part.getViewer().getDocument().get().indexOf("this.payloads.length") + "this.payloads.".length(); + IRegion region = new Region(offset, "length".length()); + String text = selectAndReveal(part, bpLine, region); + assertEquals(variableName, text); + IVariable info = (IVariable) sync(() -> hover.getHoverInfo2(part.getViewer(), region)); + + assertNotNull(info); + assertEquals("this.payloads.length", info.getName()); + assertEquals("1", info.getValue().getValueString()); + } finally { + terminateAndRemove(thread); + removeAllBreakpoints(); + } + } + public void testBug572629_ChainFieldHover_3Chains_ExpectValueFromChainAtMiddle() throws Exception { + sync(() -> TestUtil.waitForJobs(getName(), 1000, 10000, ProcessConsole.class)); + + final String typeName = "Bug572629"; + final String expectedMethod = "equals"; + final int frameNumber = 2; + final int bpLine = 33; + + IJavaBreakpoint bp = createLineBreakpoint(bpLine, "", typeName + ".java", typeName); + bp.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD); + IFile file = (IFile) bp.getMarker().getResource(); + assertEquals(typeName + ".java", file.getName()); + + IJavaThread thread = null; + try { + thread = launchToBreakpoint(typeName); + CompilationUnitEditor part = openEditorAndValidateStack(expectedMethod, frameNumber, file, thread); + + JavaDebugHover hover = new JavaDebugHover(); + hover.setEditor(part); + + String variableName = "payloads"; + int offset = part.getViewer().getDocument().get().indexOf("other.payloads.length") + "other.".length(); + IRegion region = new Region(offset, "payloads".length()); + String text = selectAndReveal(part, bpLine, region); + assertEquals(variableName, text); + IVariable info = (IVariable) sync(() -> hover.getHoverInfo2(part.getViewer(), region)); + + assertNotNull(info); + assertEquals("other.payloads", info.getName()); + assertTrue(info.getValue() instanceof IJavaArray); + } finally { + terminateAndRemove(thread); + removeAllBreakpoints(); + } + } + + public void testBug572629_ChainFieldHover_ArrayLengthOnStaticField_ExpectValue() throws Exception { + sync(() -> TestUtil.waitForJobs(getName(), 1000, 10000, ProcessConsole.class)); + + final String typeName = "Bug572629"; + final String expectedMethod = "equals"; + final int frameNumber = 2; + final int bpLine = 32; + + IJavaBreakpoint bp = createLineBreakpoint(bpLine, "", typeName + ".java", typeName); + bp.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD); + IFile file = (IFile) bp.getMarker().getResource(); + assertEquals(typeName + ".java", file.getName()); + + IJavaThread thread = null; + try { + thread = launchToBreakpoint(typeName); + CompilationUnitEditor part = openEditorAndValidateStack(expectedMethod, frameNumber, file, thread); + + JavaDebugHover hover = new JavaDebugHover(); + hover.setEditor(part); + + String variableName = "length"; + int offset = part.getViewer().getDocument().get().indexOf("PAYLOADS.length") + "PAYLOADS.".length(); + IRegion region = new Region(offset, "length".length()); + String text = selectAndReveal(part, bpLine, region); + assertEquals(variableName, text); + IVariable info = (IVariable) sync(() -> hover.getHoverInfo2(part.getViewer(), region)); + + assertNotNull(info); + assertEquals("PAYLOADS.length", info.getName()); + assertEquals("1", info.getValue().getValueString()); + } finally { + terminateAndRemove(thread); + removeAllBreakpoints(); + } + } + private CompilationUnitEditor openEditorAndValidateStack(final String expectedMethod, final int expectedFramesNumber, IFile file, IJavaThread thread) throws Exception, DebugException { // Let now all pending jobs proceed, ignore console jobs sync(() -> TestUtil.waitForJobs(getName(), 1000, 10000, ProcessConsole.class)); diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugHover.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugHover.java index dc6c0a875..d5dcaa9a1 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugHover.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugHover.java @@ -15,8 +15,14 @@ package org.eclipse.jdt.internal.debug.ui; +import java.util.Optional; +import java.util.StringJoiner; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.debug.core.DebugEvent; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.model.IVariable; import org.eclipse.debug.ui.DebugUITools; @@ -27,6 +33,7 @@ import org.eclipse.jdt.core.ICodeAssist; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IInitializer; import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.ILocalVariable; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.ITypeRoot; @@ -35,9 +42,11 @@ import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.FieldAccess; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.QualifiedName; +import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; import org.eclipse.jdt.core.dom.ThisExpression; import org.eclipse.jdt.core.manipulation.SharedASTProviderCore; @@ -49,6 +58,9 @@ import org.eclipse.jdt.debug.core.IJavaThread; import org.eclipse.jdt.debug.core.IJavaType; import org.eclipse.jdt.debug.core.IJavaValue; import org.eclipse.jdt.debug.core.IJavaVariable; +import org.eclipse.jdt.debug.eval.IAstEvaluationEngine; +import org.eclipse.jdt.debug.eval.IEvaluationListener; +import org.eclipse.jdt.debug.eval.IEvaluationResult; import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin; import org.eclipse.jdt.internal.debug.core.logicalstructures.JDIPlaceholderVariable; import org.eclipse.jdt.internal.debug.eval.ast.engine.ASTEvaluationEngine; @@ -284,20 +296,23 @@ public class JavaDebugHover implements IJavaEditorTextHover, ITextHoverExtension return resolveLocalVariable(frame, textViewer, hoverRegion); } - IJavaElement[] resolve = null; - try { - resolve = codeAssist.codeSelect(hoverRegion.getOffset(), 0); - } catch (JavaModelException e1) { - resolve = new IJavaElement[0]; - } - try { - for (int i = 0; i < resolve.length; i++) { - IJavaElement javaElement = resolve[i]; - if (javaElement instanceof IField) { - IField field = (IField)javaElement; - IJavaVariable variable = null; - IJavaDebugTarget debugTarget = (IJavaDebugTarget)frame.getDebugTarget(); - if (Flags.isStatic(field.getFlags())) { + IJavaElement[] resolve = resolveElement(hoverRegion.getOffset(), codeAssist); + try { + boolean onArrayLegnth = false; + if (resolve.length == 0 && isOverNameLength(hoverRegion, document)) { + // lets check if this is part of an array variable by jumping 2 chars backward from offset. + resolve = resolveElement(hoverRegion.getOffset() - 2, codeAssist); + onArrayLegnth = (resolve.length == 1) && (resolve[0] instanceof IField) + && ((IField) resolve[0]).getTypeSignature().startsWith("["); //$NON-NLS-1$ + } + + for (int i = 0; i < resolve.length; i++) { + IJavaElement javaElement = resolve[i]; + if (javaElement instanceof IField) { + IField field = (IField) javaElement; + IJavaVariable variable = null; + IJavaDebugTarget debugTarget = (IJavaDebugTarget) frame.getDebugTarget(); + if (Flags.isStatic(field.getFlags()) && !onArrayLegnth) { IJavaType[] javaTypes = debugTarget.getJavaTypes(field.getDeclaringType().getFullyQualifiedName()); if (javaTypes != null) { for (int j = 0; j < javaTypes.length; j++) { @@ -364,18 +379,15 @@ public class JavaDebugHover implements IJavaEditorTextHover, ITextHoverExtension StructuralPropertyDescriptor locationInParent = node.getLocationInParent(); if (locationInParent == FieldAccess.NAME_PROPERTY) { FieldAccess fieldAccess = (FieldAccess) node.getParent(); - if (!(fieldAccess.getExpression() instanceof ThisExpression)) { - return null; + if (fieldAccess.getExpression() instanceof ThisExpression) { + variable = evaluateField(frame, field); + } else if(onArrayLegnth) { + variable = evaluateQualifiedNode(fieldAccess, frame, typeRoot.getJavaProject()); } } else if (locationInParent == QualifiedName.NAME_PROPERTY) { - return null; - } - - String typeSignature = Signature.createTypeSignature(field.getDeclaringType().getFullyQualifiedName(), true); - typeSignature = typeSignature.replace('.', '/'); - IJavaObject ths = frame.getThis(); - if (ths != null) { - variable = ths.getField(field.getElementName(), typeSignature); + variable = evaluateQualifiedNode(node.getParent(), frame, typeRoot.getJavaProject()); + } else { + variable = evaluateField(frame, field); } } } @@ -456,6 +468,94 @@ public class JavaDebugHover implements IJavaEditorTextHover, ITextHoverExtension return null; } + private boolean isOverNameLength(IRegion hoverRegion, IDocument document) { + try { + return "length".equals(document.get(hoverRegion.getOffset(), hoverRegion.getLength())); //$NON-NLS-1$ + } catch (BadLocationException e) { + return false; + } + } + + private IJavaElement[] resolveElement(int offset, ICodeAssist codeAssist) { + IJavaElement[] resolve; + try { + resolve = codeAssist.codeSelect(offset, 0); + } catch (JavaModelException e1) { + resolve = new IJavaElement[0]; + } + return resolve; + } + + private IJavaVariable evaluateField(IJavaStackFrame frame, IField field) throws DebugException { + IJavaObject ths = frame.getThis(); + if (ths != null) { + String typeSignature = Signature.createTypeSignature(field.getDeclaringType().getFullyQualifiedName(), true); + typeSignature = typeSignature.replace('.', '/'); + return ths.getField(field.getElementName(), typeSignature); + } + return null; + } + + private IJavaVariable evaluateQualifiedNode(ASTNode node, IJavaStackFrame frame, IJavaProject project) { + StringBuilder snippetBuilder = new StringBuilder(); + if (node instanceof QualifiedName) { + snippetBuilder.append(((QualifiedName) node).getFullyQualifiedName()); + } else if (node instanceof FieldAccess) { + StringJoiner segments = new StringJoiner("."); //$NON-NLS-1$ + node.accept(new ASTVisitor() { + @Override + public boolean visit(SimpleName node) { + segments.add(node.getFullyQualifiedName()); + return true; + } + + @Override + public boolean visit(ThisExpression node) { + segments.add("this"); //$NON-NLS-1$ + return true; + } + + }); + snippetBuilder.append(segments.toString()); + } else { + return null; + } + + final String snippet = snippetBuilder.toString(); + class Evaluator implements IEvaluationListener { + private CompletableFuture<IEvaluationResult> result = new CompletableFuture<>(); + + @Override + public void evaluationComplete(IEvaluationResult result) { + this.result.complete(result); + } + + public void run() throws DebugException { + IAstEvaluationEngine engine = JDIDebugPlugin.getDefault().getEvaluationEngine(project, (IJavaDebugTarget) frame.getDebugTarget()); + engine.evaluate(snippet, frame, this, DebugEvent.EVALUATION, false); + } + + public Optional<IEvaluationResult> getResult() { + try { + return Optional.ofNullable(result.get()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (ExecutionException e) { + JDIDebugUIPlugin.log(e); + } + return Optional.empty(); + } + } + Evaluator evaluator = new Evaluator(); + try { + evaluator.run(); + } catch (DebugException e) { + JDIDebugUIPlugin.log(e); + } + return evaluator.getResult().flatMap(r -> Optional.ofNullable(r.getValue())) + .map(r -> new JDIPlaceholderVariable(snippet, r)).orElse(null); + } + public IInformationControlCreator getInformationPresenterControlCreator() { return new ExpressionInformationControlCreator(); } |