IDEA-366620 Evaluate and log works incorrectly in concurrent scenario

Merge-request: IJ-MR-154018
Merged-by: Egor Ushakov <Egor.Ushakov@jetbrains.com>

GitOrigin-RevId: 8b73b6442426fd45dde9b74f3989bb848cd7c401
This commit is contained in:
Egor Ushakov
2025-01-31 23:21:47 +00:00
committed by intellij-monorepo-bot
parent 5d3d9e3059
commit 11d74fb190
20 changed files with 405 additions and 385 deletions
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2015 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.
*/
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
/*
* Class ArrayAccessEvaluator
@@ -29,11 +15,16 @@ import com.intellij.debugger.ui.impl.watch.ArrayElementDescriptorImpl;
import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl;
import com.intellij.openapi.project.Project;
import com.sun.jdi.*;
import org.jetbrains.annotations.NotNull;
class ArrayAccessEvaluator implements Evaluator {
class ArrayAccessEvaluator implements ModifiableEvaluator {
private final Evaluator myArrayReferenceEvaluator;
private final Evaluator myIndexEvaluator;
// TODO remove non-final fields, see IDEA-366793
@Deprecated
private ArrayReference myEvaluatedArrayReference;
@Deprecated
private int myEvaluatedIndex;
ArrayAccessEvaluator(Evaluator arrayReferenceEvaluator, Evaluator indexEvaluator) {
@@ -42,21 +33,21 @@ class ArrayAccessEvaluator implements Evaluator {
}
@Override
public Object evaluate(EvaluationContextImpl context) throws EvaluateException {
myEvaluatedIndex = 0;
myEvaluatedArrayReference = null;
Value indexValue = (Value)myIndexEvaluator.evaluate(context);
Value arrayValue = (Value)myArrayReferenceEvaluator.evaluate(context);
if (!(arrayValue instanceof ArrayReference)) {
public @NotNull ModifiableValue evaluateModifiable(EvaluationContextImpl context) throws EvaluateException {
if (!(myArrayReferenceEvaluator.evaluate(context) instanceof ArrayReference evaluatedArrayReference)) {
throw EvaluateExceptionUtil.createEvaluateException(JavaDebuggerBundle.message("evaluation.error.array.reference.expected"));
}
myEvaluatedArrayReference = (ArrayReference)arrayValue;
Value indexValue = (Value)myIndexEvaluator.evaluate(context);
if (!DebuggerUtils.isInteger(indexValue)) {
throw EvaluateExceptionUtil.createEvaluateException(JavaDebuggerBundle.message("evaluation.error.invalid.index.expression"));
}
myEvaluatedIndex = ((PrimitiveValue)indexValue).intValue();
int evaluatedIndex = ((PrimitiveValue)indexValue).intValue();
try {
return myEvaluatedArrayReference.getValue(myEvaluatedIndex);
Value value = evaluatedArrayReference.getValue(evaluatedIndex);
myEvaluatedArrayReference = evaluatedArrayReference;
myEvaluatedIndex = evaluatedIndex;
return new ModifiableValue(value, new MyModifier(evaluatedArrayReference, evaluatedIndex));
}
catch (Exception e) {
throw EvaluateExceptionUtil.createEvaluateException(e);
@@ -65,41 +56,50 @@ class ArrayAccessEvaluator implements Evaluator {
@Override
public Modifier getModifier() {
Modifier modifier = null;
if (myEvaluatedArrayReference != null) {
modifier = new Modifier() {
@Override
public boolean canInspect() {
return true;
}
return new MyModifier(myEvaluatedArrayReference, myEvaluatedIndex);
}
return null;
}
@Override
public boolean canSetValue() {
return true;
}
private static class MyModifier implements Modifier {
private final ArrayReference myEvaluatedArrayReference;
private final int myEvaluatedIndex;
@Override
public void setValue(Value value) throws ClassNotLoadedException, InvalidTypeException {
myEvaluatedArrayReference.setValue(myEvaluatedIndex, value);
}
private MyModifier(ArrayReference evaluatedArrayReference, int evaluatedIndex) {
myEvaluatedArrayReference = evaluatedArrayReference;
myEvaluatedIndex = evaluatedIndex;
}
@Override
public Type getExpectedType() throws EvaluateException {
try {
ArrayType type = (ArrayType)myEvaluatedArrayReference.referenceType();
return type.componentType();
}
catch (ClassNotLoadedException e) {
throw EvaluateExceptionUtil.createEvaluateException(e);
}
}
@Override
public boolean canInspect() {
return true;
}
@Override
public NodeDescriptorImpl getInspectItem(Project project) {
return new ArrayElementDescriptorImpl(project, myEvaluatedArrayReference, myEvaluatedIndex);
}
};
@Override
public boolean canSetValue() {
return true;
}
@Override
public void setValue(Value value) throws ClassNotLoadedException, InvalidTypeException {
myEvaluatedArrayReference.setValue(myEvaluatedIndex, value);
}
@Override
public Type getExpectedType() throws EvaluateException {
try {
ArrayType type = (ArrayType)myEvaluatedArrayReference.referenceType();
return type.componentType();
}
catch (ClassNotLoadedException e) {
throw EvaluateExceptionUtil.createEvaluateException(e);
}
}
@Override
public NodeDescriptorImpl getInspectItem(Project project) {
return new ArrayElementDescriptorImpl(project, myEvaluatedArrayReference, myEvaluatedIndex);
}
return modifier;
}
}
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.debugger.engine.evaluation.expression;
import com.intellij.debugger.JavaDebuggerBundle;
@@ -8,7 +8,7 @@ import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
import com.sun.jdi.*;
import org.jetbrains.annotations.NotNull;
public class AssignmentEvaluator implements Evaluator {
public class AssignmentEvaluator implements ModifiableEvaluator {
private final Evaluator myLeftEvaluator;
private final Evaluator myRightEvaluator;
@@ -18,9 +18,8 @@ public class AssignmentEvaluator implements Evaluator {
}
@Override
public Object evaluate(EvaluationContextImpl context) throws EvaluateException {
myLeftEvaluator.evaluate(context);
final Modifier modifier = myLeftEvaluator.getModifier();
public @NotNull ModifiableValue evaluateModifiable(@NotNull EvaluationContextImpl context) throws EvaluateException {
Modifier modifier = myLeftEvaluator.evaluateModifiable(context).getModifier();
final Object right = myRightEvaluator.evaluate(context);
if (right != null && !(right instanceof Value)) {
@@ -29,7 +28,7 @@ public class AssignmentEvaluator implements Evaluator {
assign(modifier, right, context);
return right;
return new ModifiableValue(right, modifier);
}
static void assign(Modifier modifier, Object right, EvaluationContextImpl context) throws EvaluateException {
@@ -1,10 +1,11 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.debugger.engine.evaluation.expression;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
import org.jetbrains.annotations.NotNull;
public class BlockStatementEvaluator implements Evaluator {
public class BlockStatementEvaluator implements ModifiableEvaluator {
protected Evaluator[] myStatements;
public BlockStatementEvaluator(Evaluator[] statements) {
@@ -12,10 +13,10 @@ public class BlockStatementEvaluator implements Evaluator {
}
@Override
public Object evaluate(EvaluationContextImpl context) throws EvaluateException {
Object result = context.getVirtualMachineProxy().mirrorOfVoid();
public @NotNull ModifiableValue evaluateModifiable(@NotNull EvaluationContextImpl context) throws EvaluateException {
ModifiableValue result = new ModifiableValue(context.getVirtualMachineProxy().mirrorOfVoid(), null);
for (Evaluator statement : myStatements) {
result = statement.evaluate(context);
result = statement.evaluateModifiable(context);
}
return result;
}
@@ -40,7 +40,7 @@ public class DeconstructionPatternEvaluator extends PatternEvaluator {
res = componentEvaluator.match(objRef.getValue(field), context);
}
if (res && myVariableEvaluator != null) {
AssignmentEvaluator.assign(myVariableEvaluator.getModifier(), value, context);
AssignmentEvaluator.assign(myVariableEvaluator.evaluateModifiable(context).getModifier(), value, context);
}
return res;
}
@@ -1,4 +1,4 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.debugger.engine.evaluation.expression;
import com.intellij.debugger.engine.evaluation.EvaluateException;
@@ -9,7 +9,7 @@ import org.jetbrains.annotations.NotNull;
/**
* @author Eugene Zhuravlev
*/
public final class DisableGC implements Evaluator {
public final class DisableGC implements ModifiableEvaluator {
private final Evaluator myDelegate;
private DisableGC(@NotNull Evaluator delegate) {
@@ -24,12 +24,24 @@ public final class DisableGC implements Evaluator {
}
@Override
public Object evaluate(EvaluationContextImpl context) throws EvaluateException {
final Object result = myDelegate.evaluate(context);
if (result instanceof ObjectReference) {
context.getSuspendContext().keep((ObjectReference)result);
public @NotNull ModifiableValue evaluateModifiable(EvaluationContextImpl context) throws EvaluateException {
if (myDelegate instanceof ModifiableEvaluator modifiableEvaluator) {
ModifiableValue result = modifiableEvaluator.evaluateModifiable(context);
Object value = result.getValue();
keep(context, value);
return result;
}
else {
Object value = myDelegate.evaluate(context);
keep(context, value);
return new ModifiableValue(value, null);
}
}
private static void keep(EvaluationContextImpl context, Object value) {
if (value instanceof ObjectReference reference) {
context.keep(reference);
}
return result;
}
@Override
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.debugger.engine.evaluation.expression;
import com.intellij.debugger.engine.evaluation.EvaluateException;
@@ -15,11 +15,6 @@ public class DoWhileStatementEvaluator extends LoopEvaluator {
myConditionEvaluator = DisableGC.create(conditionEvaluator);
}
@Override
public Modifier getModifier() {
return myConditionEvaluator.getModifier();
}
@Override
public Object evaluate(EvaluationContextImpl context) throws EvaluateException {
Object value = context.getVirtualMachineProxy().mirrorOfVoid();
@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
/*
* Interface Evaluator
@@ -10,14 +10,27 @@ import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
public interface Evaluator {
Object evaluate(EvaluationContextImpl context) throws EvaluateException;
default Object evaluate(EvaluationContextImpl context) throws EvaluateException {
throw new AbstractMethodError("evaluate or evaluateModifiable must be implemented");
}
/**
* Implement if the value may be modified (like local variable or a field)
*/
default ModifiableValue evaluateModifiable(EvaluationContextImpl context) throws EvaluateException {
return new ModifiableValue(evaluate(context), getModifier());
}
/**
* In order to obtain a modifier the expression must be evaluated first
*
* @return a modifier object allowing to set a value in case the expression is lvalue,
* otherwise null is returned
*
* @deprecated implement {@link #evaluateModifiable(EvaluationContextImpl)} instead
* @see ModifiableEvaluator
*/
@Deprecated
default Modifier getModifier() {
return null;
}
@@ -936,17 +936,17 @@ public final class EvaluatorBuilderImpl implements EvaluatorBuilder {
}
private static Evaluator createFallbackEvaluator(final Evaluator primary, final Evaluator fallback) {
return new Evaluator() {
return new ModifiableEvaluator() {
private boolean myIsFallback;
@Override
public Object evaluate(EvaluationContextImpl context) throws EvaluateException {
public @NotNull ModifiableValue evaluateModifiable(@NotNull EvaluationContextImpl context) throws EvaluateException {
try {
return primary.evaluate(context);
return primary.evaluateModifiable(context);
}
catch (EvaluateException e) {
try {
Object res = fallback.evaluate(context);
ModifiableValue res = fallback.evaluateModifiable(context);
myIsFallback = true;
return res;
}
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
/*
* Class FieldEvaluator
@@ -25,11 +25,15 @@ import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class FieldEvaluator implements Evaluator {
public class FieldEvaluator implements ModifiableEvaluator {
private final Evaluator myObjectEvaluator;
private final TargetClassFilter myTargetClassFilter;
private final String myFieldName;
// TODO remove non-final fields, see IDEA-366793
@Deprecated
private Object myEvaluatedQualifier;
@Deprecated
private Field myEvaluatedField;
public interface TargetClassFilter {
@@ -94,15 +98,9 @@ public class FieldEvaluator implements Evaluator {
}
@Override
public Object evaluate(EvaluationContextImpl context) throws EvaluateException {
myEvaluatedField = null;
myEvaluatedQualifier = null;
public @NotNull ModifiableValue evaluateModifiable(EvaluationContextImpl context) throws EvaluateException {
Object object = myObjectEvaluator.evaluate(context);
return evaluateField(object, context);
}
private Object evaluateField(Object object, EvaluationContextImpl context) throws EvaluateException {
if (object instanceof ReferenceType refType) {
Field field = findField(refType);
if (field == null || !field.isStatic()) {
@@ -111,9 +109,10 @@ public class FieldEvaluator implements Evaluator {
if (field == null || !field.isStatic()) {
throw EvaluateExceptionUtil.createEvaluateException(JavaDebuggerBundle.message("evaluation.error.no.static.field", myFieldName));
}
MyModifier modifier = new MyModifier(refType, field);
myEvaluatedField = field;
myEvaluatedQualifier = refType;
return refType.getValue(field);
return new ModifiableValue(refType.getValue(field), modifier);
}
if (object instanceof ObjectReference objRef) {
@@ -125,7 +124,7 @@ public class FieldEvaluator implements Evaluator {
// expressions like 'array.length' must be treated separately
if (objRef instanceof ArrayReference && "length".equals(myFieldName)) {
return context.getVirtualMachineProxy().mirrorOf(((ArrayReference)objRef).length());
return new ModifiableValue(context.getVirtualMachineProxy().mirrorOf(((ArrayReference)objRef).length()), null);
}
Field field = findField(refType);
@@ -136,9 +135,11 @@ public class FieldEvaluator implements Evaluator {
if (field == null) {
throw EvaluateExceptionUtil.createEvaluateException(JavaDebuggerBundle.message("evaluation.error.no.instance.field", myFieldName));
}
myEvaluatedQualifier = field.isStatic() ? refType : objRef;
Object qualifier = field.isStatic() ? refType : objRef;
MyModifier modifier = new MyModifier(qualifier, field);
myEvaluatedQualifier = qualifier;
myEvaluatedField = field;
return field.isStatic() ? refType.getValue(field) : objRef.getValue(field);
return new ModifiableValue(field.isStatic() ? refType.getValue(field) : objRef.getValue(field), modifier);
}
if (object == null) {
@@ -150,48 +151,10 @@ public class FieldEvaluator implements Evaluator {
@Override
public Modifier getModifier() {
Modifier modifier = null;
if (myEvaluatedField != null && (myEvaluatedQualifier instanceof ClassType || myEvaluatedQualifier instanceof ObjectReference)) {
modifier = new Modifier() {
@Override
public boolean canInspect() {
return myEvaluatedQualifier instanceof ObjectReference;
}
@Override
public boolean canSetValue() {
return true;
}
@Override
public void setValue(Value value) throws ClassNotLoadedException, InvalidTypeException {
if (myEvaluatedQualifier instanceof ReferenceType) {
ClassType classType = (ClassType)myEvaluatedQualifier;
classType.setValue(myEvaluatedField, value);
}
else {
ObjectReference objRef = (ObjectReference)myEvaluatedQualifier;
objRef.setValue(myEvaluatedField, value);
}
}
@Override
public Type getExpectedType() throws ClassNotLoadedException {
return myEvaluatedField.type();
}
@Override
public NodeDescriptorImpl getInspectItem(Project project) {
if (myEvaluatedQualifier instanceof ObjectReference) {
return new FieldDescriptorImpl(project, (ObjectReference)myEvaluatedQualifier, myEvaluatedField);
}
else {
return null;
}
}
};
return new MyModifier(myEvaluatedQualifier, myEvaluatedField);
}
return modifier;
return null;
}
@Override
@@ -238,4 +201,50 @@ public class FieldEvaluator implements Evaluator {
return false;
}
}
private static class MyModifier implements Modifier {
private final Object myEvaluatedQualifier;
private final Field myEvaluatedField;
private MyModifier(Object qualifier, Field field) {
myEvaluatedQualifier = qualifier;
myEvaluatedField = field; }
@Override
public boolean canInspect() {
return myEvaluatedQualifier instanceof ObjectReference;
}
@Override
public boolean canSetValue() {
return true;
}
@Override
public void setValue(Value value) throws ClassNotLoadedException, InvalidTypeException {
if (myEvaluatedQualifier instanceof ReferenceType) {
ClassType classType = (ClassType)myEvaluatedQualifier;
classType.setValue(myEvaluatedField, value);
}
else {
ObjectReference objRef = (ObjectReference)myEvaluatedQualifier;
objRef.setValue(myEvaluatedField, value);
}
}
@Override
public Type getExpectedType() throws ClassNotLoadedException {
return myEvaluatedField.type();
}
@Override
public NodeDescriptorImpl getInspectItem(Project project) {
if (myEvaluatedQualifier instanceof ObjectReference) {
return new FieldDescriptorImpl(project, (ObjectReference)myEvaluatedQualifier, myEvaluatedField);
}
else {
return null;
}
}
}
}
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2017 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.
*/
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.debugger.engine.evaluation.expression;
import com.intellij.debugger.engine.evaluation.EvaluateException;
@@ -23,8 +9,6 @@ public class ForStatementEvaluator extends ForStatementEvaluatorBase {
private final Evaluator myConditionEvaluator;
private final Evaluator myUpdateEvaluator;
private Modifier myModifier;
public ForStatementEvaluator(Evaluator initializationEvaluator,
Evaluator conditionEvaluator,
Evaluator updateEvaluator,
@@ -36,16 +20,10 @@ public class ForStatementEvaluator extends ForStatementEvaluatorBase {
myUpdateEvaluator = updateEvaluator != null ? DisableGC.create(updateEvaluator) : null;
}
@Override
public Modifier getModifier() {
return myModifier;
}
@Override
protected Object evaluateInitialization(EvaluationContextImpl context, Object value) throws EvaluateException {
if (myInitializationEvaluator != null) {
value = myInitializationEvaluator.evaluate(context);
myModifier = myInitializationEvaluator.getModifier();
return myInitializationEvaluator.evaluate(context);
}
return value;
}
@@ -53,9 +31,7 @@ public class ForStatementEvaluator extends ForStatementEvaluatorBase {
@Override
protected Object evaluateCondition(EvaluationContextImpl context) throws EvaluateException {
if (myConditionEvaluator != null) {
Object value = myConditionEvaluator.evaluate(context);
myModifier = myConditionEvaluator.getModifier();
return value;
return myConditionEvaluator.evaluate(context);
}
return true;
}
@@ -63,8 +39,7 @@ public class ForStatementEvaluator extends ForStatementEvaluatorBase {
@Override
protected Object evaluateUpdate(EvaluationContextImpl context, Object value) throws EvaluateException {
if (myUpdateEvaluator != null) {
value = myUpdateEvaluator.evaluate(context);
myModifier = myUpdateEvaluator.getModifier();
return myUpdateEvaluator.evaluate(context);
}
return value;
}
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.debugger.engine.evaluation.expression;
import com.intellij.debugger.engine.JVMNameUtil;
@@ -9,85 +9,95 @@ import com.sun.jdi.ArrayReference;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.Value;
public class ForeachStatementEvaluator extends ForStatementEvaluatorBase {
public class ForeachStatementEvaluator implements Evaluator {
private final Evaluator myIterationParameterEvaluator;
private final Evaluator myIterableEvaluator;
private Evaluator myConditionEvaluator;
private Evaluator myNextEvaluator;
private int myArrayLength = -1;
private int myCurrentIndex = 0;
private Modifier myModifier;
private final Evaluator myBodyEvaluator;
private final String myLabelName;
public ForeachStatementEvaluator(Evaluator iterationParameterEvaluator,
Evaluator iterableEvaluator,
Evaluator bodyEvaluator,
String labelName) {
super(labelName, bodyEvaluator);
myIterationParameterEvaluator = iterationParameterEvaluator;
myIterableEvaluator = DisableGC.create(iterableEvaluator);
myBodyEvaluator = bodyEvaluator;
myLabelName = labelName;
}
@Override
public Modifier getModifier() {
return myModifier;
}
@Override
protected Object evaluateInitialization(EvaluationContextImpl context, Object value) throws EvaluateException {
public Object evaluate(EvaluationContextImpl context) throws EvaluateException {
final Object iterable = myIterableEvaluator.evaluate(context);
if (!(iterable instanceof ObjectReference)) {
throw new EvaluateException("Unable to do foreach for" + iterable);
}
IdentityEvaluator iterableEvaluator = new IdentityEvaluator((Value)iterable);
if (iterable instanceof ArrayReference) {
myCurrentIndex = 0;
myArrayLength = ((ArrayReference)iterable).length();
myNextEvaluator = new AssignmentEvaluator(myIterationParameterEvaluator,
new Evaluator() {
@Override
public Object evaluate(EvaluationContextImpl context) throws EvaluateException {
return ((ArrayReference)iterable).getValue(myCurrentIndex++);
}
});
return new ForStatementEvaluatorBase(myLabelName, myBodyEvaluator) {
private int myCurrentIndex = 0;
private int myArrayLength = -1;
private Evaluator myNextEvaluator;
@Override
protected Object evaluateInitialization(EvaluationContextImpl context, Object value) throws EvaluateException {
myArrayLength = ((ArrayReference)iterable).length();
myNextEvaluator = new AssignmentEvaluator(myIterationParameterEvaluator,
new Evaluator() {
@Override
public Object evaluate(EvaluationContextImpl context) {
return ((ArrayReference)iterable).getValue(myCurrentIndex++);
}
});
return value;
}
@Override
protected Object evaluateCondition(EvaluationContextImpl context) {
return myCurrentIndex < myArrayLength;
}
@Override
protected void evaluateBody(EvaluationContextImpl context) throws EvaluateException {
myNextEvaluator.evaluate(context);
super.evaluateBody(context);
}
}.evaluate(context);
}
else {
Object iterator =
new MethodEvaluator(iterableEvaluator, JVMNameUtil.getJVMRawText(CommonClassNames.JAVA_LANG_ITERABLE), "iterator", null,
new Evaluator[0]).evaluate(context);
IdentityEvaluator iteratorEvaluator = new IdentityEvaluator((Value)iterator);
myConditionEvaluator =
new MethodEvaluator(iteratorEvaluator, JVMNameUtil.getJVMRawText(CommonClassNames.JAVA_UTIL_ITERATOR), "hasNext", null,
new Evaluator[0]);
myNextEvaluator = new AssignmentEvaluator(myIterationParameterEvaluator, new MethodEvaluator(iteratorEvaluator,
JVMNameUtil.getJVMRawText(
CommonClassNames.JAVA_UTIL_ITERATOR),
"next", null, new Evaluator[0]));
}
return value;
}
return new ForStatementEvaluatorBase(myLabelName, myBodyEvaluator) {
private MethodEvaluator myConditionEvaluator;
private AssignmentEvaluator myNextEvaluator;
private boolean isArray() {
return myArrayLength > -1;
}
@Override
protected Object evaluateInitialization(EvaluationContextImpl context, Object value) throws EvaluateException {
Object iterator = new MethodEvaluator(new IdentityEvaluator((Value)iterable),
JVMNameUtil.getJVMRawText(CommonClassNames.JAVA_LANG_ITERABLE),
"iterator", null,
new Evaluator[0]).evaluate(context);
IdentityEvaluator iteratorEvaluator = new IdentityEvaluator((Value)iterator);
myConditionEvaluator = new MethodEvaluator(iteratorEvaluator,
JVMNameUtil.getJVMRawText(CommonClassNames.JAVA_UTIL_ITERATOR),
"hasNext", null,
new Evaluator[0]);
myNextEvaluator = new AssignmentEvaluator(myIterationParameterEvaluator,
new MethodEvaluator(iteratorEvaluator,
JVMNameUtil.getJVMRawText(CommonClassNames.JAVA_UTIL_ITERATOR),
"next", null,
new Evaluator[0]));
return value;
}
@Override
protected Object evaluateCondition(EvaluationContextImpl context) throws EvaluateException {
if (isArray()) {
return myCurrentIndex < myArrayLength;
}
else {
Object res = myConditionEvaluator.evaluate(context);
myModifier = myConditionEvaluator.getModifier();
return res;
}
}
@Override
protected Object evaluateCondition(EvaluationContextImpl context) throws EvaluateException {
return myConditionEvaluator.evaluate(context);
}
@Override
protected void evaluateBody(EvaluationContextImpl context) throws EvaluateException {
myNextEvaluator.evaluate(context);
super.evaluateBody(context);
@Override
protected void evaluateBody(EvaluationContextImpl context) throws EvaluateException {
myNextEvaluator.evaluate(context);
super.evaluateBody(context);
}
}.evaluate(context);
}
}
}
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.debugger.engine.evaluation.expression;
import com.intellij.debugger.engine.evaluation.EvaluateException;
@@ -11,19 +11,12 @@ public class IfStatementEvaluator implements Evaluator {
private final Evaluator myThenEvaluator;
private final Evaluator myElseEvaluator;
private Modifier myModifier;
public IfStatementEvaluator(Evaluator conditionEvaluator, Evaluator thenEvaluator, Evaluator elseEvaluator) {
myConditionEvaluator = DisableGC.create(conditionEvaluator);
myThenEvaluator = DisableGC.create(thenEvaluator);
myElseEvaluator = elseEvaluator == null ? null : DisableGC.create(elseEvaluator);
}
@Override
public Modifier getModifier() {
return myModifier;
}
@Override
public Object evaluate(EvaluationContextImpl context) throws EvaluateException {
Object value = myConditionEvaluator.evaluate(context);
@@ -33,16 +26,13 @@ public class IfStatementEvaluator implements Evaluator {
else {
if (((BooleanValue)value).booleanValue()) {
value = myThenEvaluator.evaluate(context);
myModifier = myThenEvaluator.getModifier();
}
else {
if (myElseEvaluator != null) {
value = myElseEvaluator.evaluate(context);
myModifier = myElseEvaluator.getModifier();
}
else {
value = context.getVirtualMachineProxy().mirrorOfVoid();
myModifier = null;
}
}
}
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
/*
* Class LocalVariableEvaluator
@@ -25,26 +25,32 @@ import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiVariable;
import com.sun.jdi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
class LocalVariableEvaluator implements Evaluator {
class LocalVariableEvaluator implements ModifiableEvaluator {
private static final Logger LOG = Logger.getInstance(LocalVariableEvaluator.class);
private final String myLocalVariableName;
private EvaluationContextImpl myContext;
private LocalVariableProxyImpl myEvaluatedVariable;
private DecompiledLocalVariable myEvaluatedDecompiledVariable;
private final boolean myCanScanFrames;
// TODO remove non-final fields, see IDEA-366793
@Deprecated
private EvaluationContextImpl myContext;
@Deprecated
private LocalVariableProxyImpl myEvaluatedVariable;
@Deprecated
private DecompiledLocalVariable myEvaluatedDecompiledVariable;
LocalVariableEvaluator(String localVariableName, boolean canScanFrames) {
myLocalVariableName = localVariableName;
myCanScanFrames = canScanFrames;
}
@Override
public Object evaluate(EvaluationContextImpl context) throws EvaluateException {
public @NotNull ModifiableValue evaluateModifiable(EvaluationContextImpl context) throws EvaluateException {
StackFrameProxyImpl frameProxy = context.getFrameProxy();
if (frameProxy == null) {
throw EvaluateExceptionUtil.createEvaluateException(JavaDebuggerBundle.message("evaluation.error.no.stackframe"));
@@ -66,7 +72,7 @@ class LocalVariableEvaluator implements Evaluator {
variable.equals(resolveVariable(frameProxy, myLocalVariableName, context.getProject(), process))) {
myEvaluatedVariable = local;
myContext = context;
return frameProxy.getValue(local);
return new ModifiableValue(frameProxy.getValue(local), new MyModifier(context, local, null));
}
}
}
@@ -83,7 +89,7 @@ class LocalVariableEvaluator implements Evaluator {
if (var.getMatchedNames().contains(myLocalVariableName) || var.getDefaultName().equals(myLocalVariableName)) {
myEvaluatedDecompiledVariable = var;
myContext = context;
return entry.getValue();
return new ModifiableValue(entry.getValue(), new MyModifier(context, null, var));
}
}
}
@@ -125,54 +131,10 @@ class LocalVariableEvaluator implements Evaluator {
@Override
public Modifier getModifier() {
Modifier modifier = null;
if ((myEvaluatedVariable != null || myEvaluatedDecompiledVariable != null) && myContext != null) {
modifier = new Modifier() {
@Override
public boolean canInspect() {
return true;
}
@Override
public boolean canSetValue() {
return true;
}
@Override
public void setValue(Value value) throws ClassNotLoadedException, InvalidTypeException {
StackFrameProxyImpl frameProxy = myContext.getFrameProxy();
try {
assert frameProxy != null;
if (myEvaluatedVariable != null) {
frameProxy.setValue(myEvaluatedVariable, value);
}
else { // no debug info
LocalVariablesUtil.setValue(frameProxy.getStackFrame(), myEvaluatedDecompiledVariable, value);
}
}
catch (EvaluateException e) {
LOG.error(e);
}
}
@Override
public Type getExpectedType() throws ClassNotLoadedException {
try {
return myEvaluatedVariable.getType();
}
catch (EvaluateException e) {
LOG.error(e);
return null;
}
}
@Override
public NodeDescriptorImpl getInspectItem(Project project) {
return new LocalVariableDescriptorImpl(project, myEvaluatedVariable);
}
};
return new MyModifier(myContext, myEvaluatedVariable, myEvaluatedDecompiledVariable);
}
return modifier;
return null;
}
private static @Nullable PsiVariable resolveVariable(final StackFrameProxy frame,
@@ -191,4 +153,61 @@ class LocalVariableEvaluator implements Evaluator {
public String toString() {
return myLocalVariableName;
}
private static class MyModifier implements Modifier {
private final EvaluationContextImpl myContext;
private final LocalVariableProxyImpl myEvaluatedVariable;
private final DecompiledLocalVariable myEvaluatedDecompiledVariable;
private MyModifier(EvaluationContextImpl context,
LocalVariableProxyImpl evaluatedVariable,
DecompiledLocalVariable evaluatedDecompiledVariable) {
this.myContext = context;
this.myEvaluatedVariable = evaluatedVariable;
this.myEvaluatedDecompiledVariable = evaluatedDecompiledVariable;
}
@Override
public boolean canInspect() {
return true;
}
@Override
public boolean canSetValue() {
return true;
}
@Override
public void setValue(Value value) throws ClassNotLoadedException, InvalidTypeException {
StackFrameProxyImpl frameProxy = myContext.getFrameProxy();
try {
assert frameProxy != null;
if (myEvaluatedVariable != null) {
frameProxy.setValue(myEvaluatedVariable, value);
}
else { // no debug info
LocalVariablesUtil.setValue(frameProxy.getStackFrame(), myEvaluatedDecompiledVariable, value);
}
}
catch (EvaluateException e) {
LOG.error(e);
}
}
@Override
public Type getExpectedType() throws ClassNotLoadedException {
try {
return myEvaluatedVariable.getType();
}
catch (EvaluateException e) {
LOG.error(e);
return null;
}
}
@Override
public NodeDescriptorImpl getInspectItem(Project project) {
return new LocalVariableDescriptorImpl(project, myEvaluatedVariable);
}
}
}
@@ -0,0 +1,22 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.debugger.engine.evaluation.expression
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
interface ModifiableEvaluator : Evaluator {
@Throws(EvaluateException::class)
override fun evaluate(context: EvaluationContextImpl): Any? = evaluateModifiable(context).value
@Throws(EvaluateException::class)
override fun evaluateModifiable(context: EvaluationContextImpl): ModifiableValue
}
/**
* Represents a value that can be modified. This class encapsulates a value and an optional modifier
* that allows modifications to the value.
*
* @property value The value being encapsulated, which can be of any type or null.
* @property modifier The modifier associated with the value, which provides functionality for modifying the value.
*/
class ModifiableValue(val value: Any?, val modifier: Modifier?)
@@ -40,7 +40,7 @@ public class PatternEvaluator implements Evaluator {
res = primitiveType.name().equals(myPrimitiveType);
}
if (res && myVariableEvaluator != null) {
AssignmentEvaluator.assign(myVariableEvaluator.getModifier(), value, context);
AssignmentEvaluator.assign(myVariableEvaluator.evaluateModifiable(context).getModifier(), value, context);
}
return res;
}
@@ -1,10 +1,11 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.debugger.engine.evaluation.expression;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
import org.jetbrains.annotations.NotNull;
public class PostfixOperationEvaluator implements Evaluator {
public class PostfixOperationEvaluator implements ModifiableEvaluator {
private final Evaluator myOperandEvaluator;
private final Evaluator myIncrementImpl;
@@ -17,12 +18,12 @@ public class PostfixOperationEvaluator implements Evaluator {
}
@Override
public Object evaluate(EvaluationContextImpl context) throws EvaluateException {
final Object value = myOperandEvaluator.evaluate(context);
myModifier = myOperandEvaluator.getModifier();
public @NotNull ModifiableValue evaluateModifiable(@NotNull EvaluationContextImpl context) throws EvaluateException {
ModifiableValue modifiableValue = myOperandEvaluator.evaluateModifiable(context);
myModifier = modifiableValue.getModifier();
Object operationResult = myIncrementImpl.evaluate(context);
AssignmentEvaluator.assign(myModifier, operationResult, context);
return value;
return modifiableValue;
}
@Override
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.debugger.engine.evaluation.expression;
import com.intellij.debugger.JavaDebuggerBundle;
@@ -12,14 +12,18 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.sun.jdi.Type;
import com.sun.jdi.Value;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class SyntheticVariableEvaluator implements Evaluator {
public class SyntheticVariableEvaluator implements ModifiableEvaluator {
private static final Logger LOG = Logger.getInstance(SyntheticVariableEvaluator.class);
private final CodeFragmentEvaluator myCodeFragmentEvaluator;
private final String myLocalName;
private final JVMName myTypeName;
// TODO remove non-final fields, see IDEA-366793
@Deprecated
private String myTypeNameString = null;
public SyntheticVariableEvaluator(CodeFragmentEvaluator codeFragmentEvaluator, String localName, @Nullable JVMName typeName) {
@@ -29,59 +33,65 @@ public class SyntheticVariableEvaluator implements Evaluator {
}
@Override
public Object evaluate(EvaluationContextImpl context) throws EvaluateException {
if (myTypeNameString == null && myTypeName != null) {
myTypeNameString = myTypeName.getName(context.getDebugProcess());
}
return myCodeFragmentEvaluator.getValue(myLocalName, context.getVirtualMachineProxy());
public @NotNull ModifiableValue evaluateModifiable(EvaluationContextImpl context) throws EvaluateException {
String typeNameString = myTypeName != null ? myTypeName.getName(context.getDebugProcess()) : null;
myTypeNameString = typeNameString;
return new ModifiableValue(myCodeFragmentEvaluator.getValue(myLocalName, context.getVirtualMachineProxy()),
new MyModifier(typeNameString));
}
@Override
public Modifier getModifier() {
return new Modifier() {
@Override
public boolean canInspect() {
return false;
}
@Override
public boolean canSetValue() {
return false;
}
@Override
public void setValue(Value value) throws EvaluateException {
if (value == null) {
if (myTypeNameString != null && DebuggerUtils.isPrimitiveType(myTypeNameString)) {
throw EvaluateExceptionUtil.createEvaluateException(
JavaDebuggerBundle.message("evaluation.error.cannot.set.primitive.to.null"));
}
}
else {
Type type = value.type();
if (myTypeNameString != null && !DebuggerUtils.instanceOf(type, myTypeNameString)) {
throw EvaluateExceptionUtil.createEvaluateException(
JavaDebuggerBundle.message("evaluation.error.cannot.cast.object", type.name(), myTypeNameString));
}
}
myCodeFragmentEvaluator.setValue(myLocalName, value);
}
@Override
public Type getExpectedType() {
LOG.assertTrue(false);
return null;
}
@Override
public NodeDescriptorImpl getInspectItem(Project project) {
return null;
}
};
return new MyModifier(myTypeNameString);
}
@Override
public String toString() {
return myLocalName;
}
private class MyModifier implements Modifier {
private final String myTypeNameString;
private MyModifier(String typeNameString) { this.myTypeNameString = typeNameString; }
@Override
public boolean canInspect() {
return false;
}
@Override
public boolean canSetValue() {
return false;
}
@Override
public void setValue(Value value) throws EvaluateException {
if (value == null) {
if (myTypeNameString != null && DebuggerUtils.isPrimitiveType(myTypeNameString)) {
throw EvaluateExceptionUtil.createEvaluateException(
JavaDebuggerBundle.message("evaluation.error.cannot.set.primitive.to.null"));
}
}
else {
Type type = value.type();
if (myTypeNameString != null && !DebuggerUtils.instanceOf(type, myTypeNameString)) {
throw EvaluateExceptionUtil.createEvaluateException(
JavaDebuggerBundle.message("evaluation.error.cannot.cast.object", type.name(), myTypeNameString));
}
}
myCodeFragmentEvaluator.setValue(myLocalName, value);
}
@Override
public Type getExpectedType() {
LOG.assertTrue(false);
return null;
}
@Override
public NodeDescriptorImpl getInspectItem(Project project) {
return null;
}
}
}
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
/*
* Class TypeEvaluator
@@ -13,13 +13,10 @@ import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil;
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.reference.SoftReference;
import com.sun.jdi.ClassLoaderReference;
import com.sun.jdi.ReferenceType;
import org.jetbrains.annotations.NotNull;
import java.lang.ref.WeakReference;
import static com.intellij.util.containers.ContainerUtil.filter;
import static com.intellij.util.containers.ContainerUtil.getOnlyItem;
@@ -28,9 +25,6 @@ public class TypeEvaluator implements Evaluator {
private final JVMName myTypeName;
private WeakReference<ReferenceType> myLastResult;
private WeakReference<ClassLoaderReference> myLastClassLoader;
public TypeEvaluator(@NotNull JVMName typeName) {
myTypeName = typeName;
}
@@ -41,13 +35,6 @@ public class TypeEvaluator implements Evaluator {
@Override
public @NotNull ReferenceType evaluate(EvaluationContextImpl context) throws EvaluateException {
ClassLoaderReference classLoader = context.getClassLoader();
ReferenceType lastRes = SoftReference.dereference(myLastResult);
if (lastRes != null && classLoader == SoftReference.dereference(myLastClassLoader)) {
// if class loader is null, check that vms match
if (classLoader != null || lastRes.virtualMachine().equals(context.getVirtualMachineProxy().getVirtualMachine())) {
return lastRes;
}
}
DebugProcessImpl debugProcess = context.getDebugProcess();
String typeName = myTypeName.getName(debugProcess);
ReferenceType type;
@@ -69,8 +56,6 @@ public class TypeEvaluator implements Evaluator {
if (type == null) {
throw EvaluateExceptionUtil.createEvaluateException(JavaDebuggerBundle.message("error.class.not.loaded", typeName));
}
myLastClassLoader = new WeakReference<>(classLoader);
myLastResult = new WeakReference<>(type);
return type;
}
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2017 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.
*/
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.debugger.engine.evaluation.expression;
import com.intellij.debugger.engine.evaluation.EvaluateException;
@@ -29,11 +15,6 @@ public class WhileStatementEvaluator extends LoopEvaluator {
myConditionEvaluator = DisableGC.create(conditionEvaluator);
}
@Override
public Modifier getModifier() {
return myConditionEvaluator.getModifier();
}
@Override
public Object evaluate(EvaluationContextImpl context) throws EvaluateException {
Object value;
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.debugger.evaluate
@@ -489,8 +489,6 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, private val sourcePositi
}
}
override fun getModifier() = null
companion object {
@get:TestOnly
@get:ApiStatus.Internal