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 c66fdbb730..4bd8f2d611 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 @@ -405,6 +405,7 @@ void assert15Project() { cfgs.add(createLaunchConfiguration(jp, "a.b.c.bug329294WithGenerics")); cfgs.add(createLaunchConfiguration(jp, "a.b.c.bug403028")); cfgs.add(createLaunchConfiguration(jp, "a.b.c.bug484686")); + cfgs.add(createLaunchConfiguration(jp, "a.b.c.PrimitivesTest")); cfgs.add(createLaunchConfiguration(jp, "a.b.c.GenericMethodEntryTest")); cfgs.add(createLaunchConfiguration(jp, "org.eclipse.debug.tests.targets.HcrClass", true)); cfgs.add(createLaunchConfiguration(jp, "a.b.c.Bug570988")); diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/variables/DetailFormatterTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/variables/DetailFormatterTests.java index 0c685e5c07..b9c766474e 100644 --- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/variables/DetailFormatterTests.java +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/variables/DetailFormatterTests.java @@ -340,4 +340,131 @@ public void testHoverWithNoTypeArguments() throws Exception { removeAllBreakpoints(); } } + + public void testFormatterForPrimitivesInt() throws Exception { + IJavaThread thread = null; + DetailFormatter formatter = null; + JavaDetailFormattersManager jdfm = JavaDetailFormattersManager.getDefault(); + try { + String typename = "a.b.c.PrimitivesTest"; + createLineBreakpoint(26, typename); + thread = launchToBreakpoint(typename); + assertNotNull("The program did not suspend", thread); + String snippet = "this + 12"; + formatter = new DetailFormatter("int", snippet, true); + jdfm.setAssociatedDetailFormatter(formatter); + IJavaVariable var = thread.findVariable("x"); + assertNotNull("the variable 'x' must exist in the frame", var); + jdfm.computeValueDetail((IJavaValue) var.getValue(), thread, fListener); + waitForListenerValue(); + assertNotNull("The IValue of the detailComputed callback cannot be null", fListener.value); + assertEquals("24", fListener.result.toString()); + + } finally { + jdfm.removeAssociatedDetailFormatter(formatter); + terminateAndRemove(thread); + removeAllBreakpoints(); + } + } + + public void testFormatterForPrimitivesfloat() throws Exception { + IJavaThread thread = null; + DetailFormatter formatter = null; + JavaDetailFormattersManager jdfm = JavaDetailFormattersManager.getDefault(); + try { + String typename = "a.b.c.PrimitivesTest"; + createLineBreakpoint(26, typename); + thread = launchToBreakpoint(typename); + assertNotNull("The program did not suspend", thread); + String snippet = "this + 10"; + formatter = new DetailFormatter("float", snippet, true); + jdfm.setAssociatedDetailFormatter(formatter); + IJavaVariable var = thread.findVariable("f"); + assertNotNull("the variable 'f' must exist in the frame", var); + jdfm.computeValueDetail((IJavaValue) var.getValue(), thread, fListener); + waitForListenerValue(); + assertNotNull("The IValue of the detailComputed callback cannot be null", fListener.value); + assertEquals("20.0", fListener.result.toString()); + } finally { + jdfm.removeAssociatedDetailFormatter(formatter); + terminateAndRemove(thread); + removeAllBreakpoints(); + } + } + + public void testFormatterForPrimitivesIntArrays() throws Exception { + IJavaThread thread = null; + DetailFormatter formatter = null; + JavaDetailFormattersManager jdfm = JavaDetailFormattersManager.getDefault(); + try { + String typename = "a.b.c.PrimitivesTest"; + createLineBreakpoint(26, typename); + thread = launchToBreakpoint(typename); + assertNotNull("The program did not suspend", thread); + String snippet = "this[1]"; + formatter = new DetailFormatter("int[]", snippet, true); + jdfm.setAssociatedDetailFormatter(formatter); + IJavaVariable var = thread.findVariable("arInt"); + assertNotNull("the variable 'arInt' must exist in the frame", var); + jdfm.computeValueDetail((IJavaValue) var.getValue(), thread, fListener); + waitForListenerValue(); + assertNotNull("The IValue of the detailComputed callback cannot be null", fListener.value); + assertEquals("2", fListener.result.toString()); + } finally { + jdfm.removeAssociatedDetailFormatter(formatter); + terminateAndRemove(thread); + removeAllBreakpoints(); + } + } + + public void testFormatterForPrimitivesIntArraysMulti() throws Exception { + IJavaThread thread = null; + DetailFormatter formatter = null; + JavaDetailFormattersManager jdfm = JavaDetailFormattersManager.getDefault(); + try { + String typename = "a.b.c.PrimitivesTest"; + createLineBreakpoint(26, typename); + thread = launchToBreakpoint(typename); + assertNotNull("The program did not suspend", thread); + String snippet = "this[0][0]"; + formatter = new DetailFormatter("int[][]", snippet, true); + jdfm.setAssociatedDetailFormatter(formatter); + IJavaVariable var = thread.findVariable("mul"); + assertNotNull("the variable 'mul' must exist in the frame", var); + jdfm.computeValueDetail((IJavaValue) var.getValue(), thread, fListener); + waitForListenerValue(); + assertNotNull("The IValue of the detailComputed callback cannot be null", fListener.value); + assertEquals("1", fListener.result.toString()); + } finally { + jdfm.removeAssociatedDetailFormatter(formatter); + terminateAndRemove(thread); + removeAllBreakpoints(); + } + } + + public void testFormatterForPrimitivesCharArray() throws Exception { + IJavaThread thread = null; + DetailFormatter formatter = null; + JavaDetailFormattersManager jdfm = JavaDetailFormattersManager.getDefault(); + try { + String typename = "a.b.c.PrimitivesTest"; + createLineBreakpoint(26, typename); + thread = launchToBreakpoint(typename); + assertNotNull("The program did not suspend", thread); + String snippet = "new String(this)"; + formatter = new DetailFormatter("char[]", snippet, true); + jdfm.setAssociatedDetailFormatter(formatter); + IJavaVariable var = thread.findVariable("aCh"); + assertNotNull("the variable 'aCh' must exist in the frame", var); + jdfm.computeValueDetail((IJavaValue) var.getValue(), thread, fListener); + waitForListenerValue(); + assertNotNull("The IValue of the detailComputed callback cannot be null", fListener.value); + assertEquals("ab", fListener.result.toString()); + } finally { + jdfm.removeAssociatedDetailFormatter(formatter); + terminateAndRemove(thread); + removeAllBreakpoints(); + } + } + } diff --git a/org.eclipse.jdt.debug.tests/testsource-j2se-1.5/a/b/c/PrimitivesTest.java b/org.eclipse.jdt.debug.tests/testsource-j2se-1.5/a/b/c/PrimitivesTest.java new file mode 100644 index 0000000000..fb32332b63 --- /dev/null +++ b/org.eclipse.jdt.debug.tests/testsource-j2se-1.5/a/b/c/PrimitivesTest.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package a.b.c; + +public class PrimitivesTest { + public static void main(String[] args) { + Double xd = Double.valueOf(1); + int x = 12; + float f = 10; + int[] arInt = { 1, 2, 3 }; + char[] aCh = { 'a', 'b' }; + boolean b = false; + double[] d = { 1, 3, 4 }; + int[][] mul = { { 1, 3, 4 }, { 1, 3, 4 } }; + int p1 = 120; + } +} diff --git a/org.eclipse.jdt.debug.ui/plugin.xml b/org.eclipse.jdt.debug.ui/plugin.xml index c68914fc4c..2a878ca57b 100644 --- a/org.eclipse.jdt.debug.ui/plugin.xml +++ b/org.eclipse.jdt.debug.ui/plugin.xml @@ -753,22 +753,12 @@ id="org.eclipse.jdt.debug.ui.FilteredJavaVariableActions"> - - - - - - - - - - - 0) { + String s = (String) types[0]; + fTypeNameText.setText(s); + fTypeSearched = true; + } + } + + /** + * Returns a List of available primitives and arrays + * + * @return List primitives and arrays + */ + @SuppressWarnings("nls") + private List getPrimitiveTypes() { + return List.of("int", "long", "short", "float", "double", "boolean", "char", "byte", "int[]", "long[]", "short[]", "float[]", "double[]", "boolean[]", "char[]", "byte[]"); + } + /** * Use the Java search engine to find the type which corresponds * to the given name. diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDetailFormattersManager.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDetailFormattersManager.java index b7c54d7f72..29282f3706 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDetailFormattersManager.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDetailFormattersManager.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2019 IBM Corporation and others. + * Copyright (c) 2000, 2025 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -57,6 +57,7 @@ import org.eclipse.jdt.internal.debug.core.logicalstructures.JDIAllInstancesValue; import org.eclipse.jdt.internal.debug.core.model.JDINullValue; import org.eclipse.jdt.internal.debug.core.model.JDIReferenceListValue; +import org.eclipse.jdt.internal.debug.core.model.JDIType; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.util.Util; @@ -143,7 +144,7 @@ public void run() { } private void resolveFormatter(final IJavaValue value, final IJavaThread thread, final IValueDetailListener listener) { - EvaluationListener evaluationListener= new EvaluationListener(value, thread, listener); + EvaluationListener evaluationListener = new EvaluationListener(value, thread, listener); if (value instanceof IJavaObject) { IJavaObject objectValue= (IJavaObject) value; try { @@ -168,6 +169,19 @@ private void resolveFormatter(final IJavaValue value, final IJavaThread thread, return; } } + if (value instanceof IJavaPrimitiveValue primeValue) { + try { + IJavaDebugTarget debugTarget= (IJavaDebugTarget) thread.getDebugTarget(); + Expression expression= getCompiledExpression(primeValue, debugTarget, thread); + if (expression != null) { + expression.getEngine().evaluateExpression(expression.getExpression(), primeValue, thread, evaluationListener, DebugEvent.EVALUATION_IMPLICIT, false); + return; + } + } catch (CoreException e) { + listener.detailComputed(value, e.toString()); + return; + } + } try { evaluationListener.valueToString(value); } catch (DebugException e) { @@ -181,7 +195,7 @@ private void resolveFormatter(final IJavaValue value, final IJavaThread thread, } } - private IJavaProject getJavaProject(IJavaObject javaValue, IJavaThread thread) throws CoreException { + private IJavaProject getJavaProject(IJavaValue javaValue, IJavaThread thread) throws CoreException { IType type = null; if (javaValue instanceof IJavaArray) { @@ -285,11 +299,13 @@ public boolean hasAssociatedDetailFormatter(IJavaType type) { public DetailFormatter getAssociatedDetailFormatter(IJavaType type) { String typeName = Util.ZERO_LENGTH_STRING; try { - while (type instanceof IJavaArrayType) { - type = ((IJavaArrayType)type).getComponentType(); + if (type instanceof IJavaArrayType javaArray) { + typeName = javaArray.getName(); } if (type instanceof IJavaClassType) { typeName = type.getName(); + } else if (type instanceof JDIType) { + typeName = type.getName(); } else { return null; @@ -299,6 +315,7 @@ public DetailFormatter getAssociatedDetailFormatter(IJavaType type) { return fDetailFormattersMap.get(typeName); } + public void setAssociatedDetailFormatter(DetailFormatter detailFormatter) { fDetailFormattersMap.put(detailFormatter.getTypeName(), detailFormatter); savePreference(); @@ -341,6 +358,23 @@ private String getDetailFormatter(IJavaClassType type) throws DebugException { return null; } + /** + * Return the detail formatter (code snippet) associate with the given type + * + * @param type + * the JDIType type + * @return the code snippet for the given JDIType + * @throws DebugException + * if there is problem computing the snippet + */ + private String getDetailFormatter(JDIType type) throws DebugException { + String snippet = getDetailFormatterForPrimitives(type); + if (snippet != null) { + return snippet; + } + return null; + } + /** * Return the detail formatter (code snippet) associate with * the given type or one of its super types. @@ -359,6 +393,34 @@ private String getDetailFormatterSuperClass(IJavaClassType type) throws DebugExc return getDetailFormatterSuperClass(type.getSuperclass()); } + /** + * Return the detail formatter (code snippet) associate with the given type or one of its super types. + * + * @param type + * the Array type + * @return the snippet for the given class / super class + * @throws DebugException + * if there is a problem computing the snippet + */ + private String getDetailFormatterFromArray(IJavaArrayType type) throws DebugException { + if (type == null) { + return null; + } + DetailFormatter detailFormatter = fDetailFormattersMap.get(type.getName()); + if (detailFormatter != null && detailFormatter.isEnabled()) { + return detailFormatter.getSnippet(); + } + return null; + } + + private String getDetailFormatterForPrimitives(JDIType type) throws DebugException { + DetailFormatter detailFormatter = fDetailFormattersMap.get(type.getName()); + if (detailFormatter != null && detailFormatter.isEnabled()) { + return detailFormatter.getSnippet(); + } + return null; + } + /** * Return the expression which corresponds to the code formatter associated with the type of * the given object or null if none. @@ -385,12 +447,13 @@ private Expression getCompiledExpression(IJavaObject javaObject, IJavaDebugTarge if (type instanceof IJavaClassType) { snippet = getDetailFormatter((IJavaClassType) type); } - if (type instanceof IJavaArrayType) { + if (type instanceof IJavaArrayType jArray) { if (JavaCore.compareJavaVersions(debugTarget.getVersion(), JavaCore.VERSION_9) < 0) { snippet = getArraySnippet((IJavaArray) javaObject); + } else { + snippet = getDetailFormatterFromArray(jArray); } } - if (snippet != null) { IJavaProject project = getJavaProject(javaObject, thread); if (project != null) { @@ -408,6 +471,101 @@ private Expression getCompiledExpression(IJavaObject javaObject, IJavaDebugTarge return null; } + /** + * Return the expression which corresponds to the code formatter associated with the type of the given primitive or null if none. + * + * The code snippet is compiled in the context of the given object. + * + * @param IJavaPrimitiveValue + * the primitive object + * @param debugTarget + * the target + * @param thread + * the thread context + * @return the compiled expression to be evaluated + * @throws CoreException + * is a problem occurs compiling the expression + */ + private Expression getCompiledExpression(IJavaPrimitiveValue javaPM, IJavaDebugTarget debugTarget, IJavaThread thread) throws CoreException { + IJavaType type = javaPM.getJavaType(); + if (type == null) { + return null; + } + String snippet = null; + + if (type instanceof JDIType jdiType) { + snippet = getDetailFormatter(jdiType); + if (snippet == null) { + return null; + } + snippet = primitiveSnippets(snippet, jdiType.getName(), javaPM); + } + if (type instanceof IJavaArrayType) { + if (JavaCore.compareJavaVersions(debugTarget.getVersion(), JavaCore.VERSION_9) < 0) { + snippet = getArraySnippet((IJavaArray) javaPM); + } + } + if (snippet != null) { + IJavaProject project = getJavaProject(javaPM, thread); + if (project != null) { + IAstEvaluationEngine evaluationEngine = JDIDebugPlugin.getDefault().getEvaluationEngine(project, debugTarget); + ICompiledExpression res = evaluationEngine.getCompiledExpression(snippet, (IJavaStackFrame) thread.getTopStackFrame()); + if (res != null) { + Expression exp = new Expression(res, evaluationEngine); + return exp; + } + } + } + return null; + } + + /** + * Return the modified expression which replaces this keyword used in formatter + * + * The code snippet is compiled in the context of the given object. + * + * @param snippet + * Original snippet + * @param typeName + * type of the primitive + * @param primitiveValue + * primitive object + * @return modified expression to be evaluated + */ + @SuppressWarnings("nls") + private String primitiveSnippets(String snippet, String typeName, IJavaPrimitiveValue primitiveValue) { + String modified = null; + if (typeName.equals("float")) { + String thisValue = Float.valueOf(primitiveValue.getFloatValue()).toString(); + modified = snippet.replace("this", thisValue); + } else if (typeName.equals("int")) { + String thisValue = Integer.valueOf(primitiveValue.getIntValue()).toString(); + modified = snippet.replace("this", thisValue); + } else if (typeName.equals("byte")) { + String thisValue = Byte.valueOf(primitiveValue.getByteValue()).toString(); + modified = snippet.replace("this", thisValue); + } else if (typeName.equals("long")) { + String thisValue = Long.valueOf(primitiveValue.getLongValue()).toString(); + modified = snippet.replace("this", thisValue); + } else if (typeName.equals("short")) { + String thisValue = Short.valueOf(primitiveValue.getShortValue()).toString(); + modified = snippet.replace("this", thisValue); + } else if (typeName.equals("double")) { + String thisValue = Double.valueOf(primitiveValue.getDoubleValue()).toString(); + modified = snippet.replace("this", thisValue); + } else if (typeName.equals("boolean")) { + String thisValue = Boolean.valueOf(primitiveValue.getBooleanValue()).toString(); + modified = snippet.replace("this", thisValue); + } else if (typeName.equals("char")) { + String thisValue = Character.valueOf(primitiveValue.getCharValue()).toString(); + modified = snippet.replace("this", thisValue); + } else { + modified = snippet; + } + return modified; + + } + protected String getArraySnippet(IJavaArray value) throws DebugException { String signature = value.getSignature(); int nesting = Signature.getArrayCount(signature); diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaVarActionFilter.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaVarActionFilter.java index 447fd29a01..c3a61d1a53 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaVarActionFilter.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaVarActionFilter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2005, 2017 IBM Corporation and others. + * Copyright (c) 2005, 2025 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -18,11 +18,13 @@ import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.model.IValue; +import org.eclipse.jdt.debug.core.IJavaArray; import org.eclipse.jdt.debug.core.IJavaArrayType; import org.eclipse.jdt.debug.core.IJavaClassType; import org.eclipse.jdt.debug.core.IJavaDebugTarget; import org.eclipse.jdt.debug.core.IJavaFieldVariable; import org.eclipse.jdt.debug.core.IJavaObject; +import org.eclipse.jdt.debug.core.IJavaPrimitiveValue; import org.eclipse.jdt.debug.core.IJavaType; import org.eclipse.jdt.debug.core.IJavaVariable; import org.eclipse.jdt.internal.debug.core.model.JDINullValue; @@ -227,6 +229,14 @@ else if (name.equals("DetailFormatterFilter") && (varValue instanceof IJavaObjec if(value.equals("inSuperclass")) { //$NON-NLS-1$ return JavaDetailFormattersManager.getDefault().hasSuperclassDetailFormatter(((IJavaObject)varValue).getJavaType()); } + } else if (name.equals("DetailFormatterFilter") && (varValue instanceof IJavaPrimitiveValue javaPrime)) { //$NON-NLS-1$ + if (value.equals("isDefined")) { //$NON-NLS-1$ + return JavaDetailFormattersManager.getDefault().hasAssociatedDetailFormatter(javaPrime.getJavaType()); + } + } else if (name.equals("DetailFormatterFilter") && (varValue instanceof IJavaArray javaArray)) { //$NON-NLS-1$ + if (value.equals("isDefined")) { //$NON-NLS-1$ + return JavaDetailFormattersManager.getDefault().hasAssociatedDetailFormatter(javaArray.getJavaType()); + } } } catch (DebugException e) {} } diff --git a/org.eclipse.jdt.debug/.classpath b/org.eclipse.jdt.debug/.classpath index 3bc414e51b..db8267d6b1 100644 --- a/org.eclipse.jdt.debug/.classpath +++ b/org.eclipse.jdt.debug/.classpath @@ -1,6 +1,6 @@ - + diff --git a/org.eclipse.jdt.debug/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jdt.debug/.settings/org.eclipse.jdt.core.prefs index 311a6ec65e..0833f0fb5f 100644 --- a/org.eclipse.jdt.debug/.settings/org.eclipse.jdt.core.prefs +++ b/org.eclipse.jdt.debug/.settings/org.eclipse.jdt.core.prefs @@ -17,9 +17,9 @@ org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nul org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate -org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=21 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.compliance=21 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -129,7 +129,7 @@ org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedWarningToken=error org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning org.eclipse.jdt.core.compiler.release=enabled -org.eclipse.jdt.core.compiler.source=17 +org.eclipse.jdt.core.compiler.source=21 org.eclipse.jdt.core.compiler.taskCaseSensitive=enabled org.eclipse.jdt.core.compiler.taskPriorities=NORMAL,HIGH,NORMAL,HIGH,HIGH org.eclipse.jdt.core.compiler.taskTags=TODO,FIXME,XXX,EXPERIMENTAL,CONTEXTLAUNCHING diff --git a/org.eclipse.jdt.debug/META-INF/MANIFEST.MF b/org.eclipse.jdt.debug/META-INF/MANIFEST.MF index 84d4ebd713..bcbf1729bd 100644 --- a/org.eclipse.jdt.debug/META-INF/MANIFEST.MF +++ b/org.eclipse.jdt.debug/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.jdt.debug; singleton:=true -Bundle-Version: 3.21.700.qualifier +Bundle-Version: 3.22.0.qualifier Bundle-ClassPath: jdimodel.jar Bundle-Activator: org.eclipse.jdt.internal.debug.core.JDIDebugPlugin Bundle-Vendor: %providerName @@ -31,6 +31,6 @@ Require-Bundle: org.eclipse.core.resources;bundle-version="[3.19.0,4.0.0)", org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)", org.eclipse.core.expressions;bundle-version="[3.9.0,4.0.0)" Bundle-ActivationPolicy: lazy -Bundle-RequiredExecutionEnvironment: JavaSE-17 +Bundle-RequiredExecutionEnvironment: JavaSE-21 Eclipse-BundleShape: dir Automatic-Module-Name: org.eclipse.jdt.debug diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/debug/eval/IAstEvaluationEngine.java b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/debug/eval/IAstEvaluationEngine.java index 394f322f36..f6a66e89d0 100644 --- a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/debug/eval/IAstEvaluationEngine.java +++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/debug/eval/IAstEvaluationEngine.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2011 IBM Corporation and others. + * Copyright (c) 2000, 2025 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -17,6 +17,7 @@ import org.eclipse.debug.core.DebugException; import org.eclipse.jdt.debug.core.IJavaObject; +import org.eclipse.jdt.debug.core.IJavaPrimitiveValue; import org.eclipse.jdt.debug.core.IJavaReferenceType; import org.eclipse.jdt.debug.core.IJavaStackFrame; import org.eclipse.jdt.debug.core.IJavaThread; @@ -132,6 +133,43 @@ public void evaluateExpression(ICompiledExpression expression, IEvaluationListener listener, int evaluationDetail, boolean hitBreakpoints) throws DebugException; + /** + * Asynchronously evaluates the given expression in the context of the specified type, reporting the result back to the given listener. The + * expression is evaluated in the context of the Java project this evaluation engine was created on. If the expression is determined to have no + * errors, the expression is evaluated in the thread associated with the given stack frame. When the evaluation completes, the thread will be + * suspended at this original location. The thread runs the evaluation with the given evaluation detail (@see + * IJavaThread#runEvaluation(IEvaluationRunnable, IProgressMonitor, int)). Compilation and runtime errors are reported in the evaluation result. + * + * @param expression + * the expression to evaluate + * @param object + * primitive 'this' context for the evaluation + * @param thread + * the thread in which to run the evaluation, which must be suspended + * @param listener + * the listener that will receive notification when/if the evaluation completes + * @param evaluationDetail + * bitmask of one of DebugEvent.EVALUATION or DebugEvent.EVALUATION_IMPLICIT and optionally + * DISABLE_GC_ON_RESULT + * @param hitBreakpoints + * whether or not breakpoints should be honored in the evaluation thread during the evaluation. If false, breakpoints hit + * in the evaluation thread will be ignored. + * @exception DebugException + * if this method fails. Reasons include: + *
    + *
  • Failure communicating with the VM. The DebugException's status code contains the underlying exception responsible for the + * failure.
  • + *
  • The associated thread is not currently suspended
  • + *
  • The stack frame is not contained in the debug target associated with this evaluation engine
  • + *
  • The associated thread is suspended in the middle of an evaluation that has not completed. It is not possible to perform + * nested evaluations
  • + *
+ * @since 3.22 + */ + public void evaluateExpression(ICompiledExpression expression, + IJavaPrimitiveValue object, IJavaThread thread, + IEvaluationListener listener, int evaluationDetail, + boolean hitBreakpoints) throws DebugException; /** * Synchronously generates a compiled expression from the given expression * in the context of the specified stack frame. The generated expression can diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTEvaluationEngine.java b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTEvaluationEngine.java index 5a398535cf..21c2a7caab 100644 --- a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTEvaluationEngine.java +++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTEvaluationEngine.java @@ -53,6 +53,7 @@ import org.eclipse.jdt.debug.core.IJavaArrayType; import org.eclipse.jdt.debug.core.IJavaDebugTarget; import org.eclipse.jdt.debug.core.IJavaObject; +import org.eclipse.jdt.debug.core.IJavaPrimitiveValue; import org.eclipse.jdt.debug.core.IJavaReferenceType; import org.eclipse.jdt.debug.core.IJavaStackFrame; import org.eclipse.jdt.debug.core.IJavaThread; @@ -216,6 +217,25 @@ public void evaluateExpression(ICompiledExpression expression, IJavaObject thisC doEvaluation(expression, context, thread, listener, evaluationDetail, hitBreakpoints); } + /* + * (non-Javadoc) + * + * @see org.eclipse.jdt.debug.eval.IAstEvaluationEngine#evaluateExpression(org .eclipse.jdt.debug.eval.ICompiledExpression, + * org.eclipse.jdt.debug.core.IJavaPrimitiveValue, org.eclipse.jdt.debug.core.IJavaThread, org.eclipse.jdt.debug.eval.IEvaluationListener, int, + * boolean) + */ + @Override + public void evaluateExpression(ICompiledExpression expression, IJavaPrimitiveValue thisContext, IJavaThread thread, IEvaluationListener listener, int evaluationDetail, boolean hitBreakpoints) throws DebugException { + traceCaller(expression.getSnippet(), thread); + IRuntimeContext context = null; + if (thisContext instanceof IJavaArray) { + context = new ArrayRuntimeContext((IJavaArray) thisContext, thread, getJavaProject()); + } else { + context = new JavaPrimitiveRuntimeContext(thisContext, getJavaProject(), thread); + } + doEvaluation(expression, context, thread, listener, evaluationDetail, hitBreakpoints); + } + /** * Evaluates the given expression in the given thread and the given runtime context. */ diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/IRuntimeContext.java b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/IRuntimeContext.java index 9211dea19a..98f9a1d0c6 100644 --- a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/IRuntimeContext.java +++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/IRuntimeContext.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2011 IBM Corporation and others. + * Copyright (c) 2000, 2025 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -19,6 +19,7 @@ import org.eclipse.jdt.debug.core.IJavaClassObject; import org.eclipse.jdt.debug.core.IJavaDebugTarget; import org.eclipse.jdt.debug.core.IJavaObject; +import org.eclipse.jdt.debug.core.IJavaPrimitiveValue; import org.eclipse.jdt.debug.core.IJavaReferenceType; import org.eclipse.jdt.debug.core.IJavaStackFrame; import org.eclipse.jdt.debug.core.IJavaThread; @@ -55,6 +56,16 @@ public interface IRuntimeContext { */ IJavaObject getThis() throws CoreException; + /** + * Returns the receiving primitive type in which to perform the evaluation - equivalent to 'this'. Returns null + * + * @return 'this', or null + * @since 3.21 + */ + default IJavaPrimitiveValue getThisPrimitive() { + return null; + } + /** * Returns the receiving type context in which to perform the evaluation. The type of 'this', or in the case of a static context, the class or * interface in which the evaluation is being performed. diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/JavaPrimitiveRuntimeContext.java b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/JavaPrimitiveRuntimeContext.java new file mode 100644 index 0000000000..be0a8290a4 --- /dev/null +++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/JavaPrimitiveRuntimeContext.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.debug.eval.ast.engine; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.debug.core.IJavaDebugTarget; +import org.eclipse.jdt.debug.core.IJavaObject; +import org.eclipse.jdt.debug.core.IJavaPrimitiveValue; +import org.eclipse.jdt.debug.core.IJavaReferenceType; +import org.eclipse.jdt.debug.core.IJavaThread; +import org.eclipse.jdt.debug.core.IJavaVariable; + +public class JavaPrimitiveRuntimeContext extends AbstractRuntimeContext { + /** + * this object or this context. + */ + private final IJavaPrimitiveValue fThisPrimitive; + + /** + * The thread for this context. + */ + private final IJavaThread fThread; + + /** + * ObjectValueRuntimeContext constructor. + * + * @param thisObject + * this object of this context. + * @param javaProject + * the project for this context. + * @param thread + * the thread for this context. + */ + public JavaPrimitiveRuntimeContext(IJavaPrimitiveValue thisObject, IJavaProject javaProject, IJavaThread thread) { + super(javaProject); + fThisPrimitive = thisObject; + fThread = thread; + } + + /** + * @see IRuntimeContext#getVM() + */ + @Override + public IJavaDebugTarget getVM() { + return (IJavaDebugTarget) fThisPrimitive.getDebugTarget(); + } + + /** + * @see IRuntimeContext#getThis() + */ + @Override + public IJavaPrimitiveValue getThisPrimitive() { + return fThisPrimitive; + } + + /** + * @see IRuntimeContext#getReceivingType() + */ + @Override + public IJavaReferenceType getReceivingType() throws CoreException { + return (IJavaReferenceType) getThisPrimitive().getJavaType(); + } + + /** + * @see IRuntimeContext#getLocals() + */ + @Override + public IJavaVariable[] getLocals() { + return new IJavaVariable[0]; + } + + /** + * @see IRuntimeContext#getThread() + */ + @Override + public IJavaThread getThread() { + return fThread; + } + + /** + * @see IRuntimeContext#isConstructor() + */ + @Override + public boolean isConstructor() { + return false; + } + + @Override + public IJavaObject getThis() throws CoreException { + return null; + } + +} diff --git a/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/ThreadReferenceImpl.java b/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/ThreadReferenceImpl.java index 67301de32e..6895b7bf32 100644 --- a/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/ThreadReferenceImpl.java +++ b/org.eclipse.jdt.debug/jdi/org/eclipse/jdi/internal/ThreadReferenceImpl.java @@ -363,6 +363,7 @@ public boolean isSuspended() { * @return true if the thread is a virtual thread * @since 3.20 */ + @Override public boolean isVirtual() { if (isVirtualCached) { return isVirtual;