From 939f6ed53056a08a63220a05984dd37b6e212653 Mon Sep 17 00:00:00 2001 From: Eugene Zhuravlev Date: Tue, 9 Feb 2010 15:30:36 +0300 Subject: [PATCH] autoboxing support for debugger evaluators: binary/unary expressions, assignments, casts (IDEA-36597) --- .../intellij/debugger/actions/SetValueAction.java | 17 ++- .../evaluation/expression/BoxingEvaluator.java | 98 +++++++++++++++ .../expression/EvaluatorBuilderImpl.java | 135 +++++++++++++++++---- .../evaluation/expression/IdentityEvaluator.java | 40 ++++++ .../evaluation/expression/UnBoxingEvaluator.java | 90 ++++++++++++++ 5 files changed, 353 insertions(+), 27 deletions(-) create mode 100644 java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/BoxingEvaluator.java create mode 100644 java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/IdentityEvaluator.java create mode 100644 java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/UnBoxingEvaluator.java diff --git a/java/debugger/impl/src/com/intellij/debugger/actions/SetValueAction.java b/java/debugger/impl/src/com/intellij/debugger/actions/SetValueAction.java index 2996a63f2b..288275b3a1 100644 --- a/java/debugger/impl/src/com/intellij/debugger/actions/SetValueAction.java +++ b/java/debugger/impl/src/com/intellij/debugger/actions/SetValueAction.java @@ -20,9 +20,7 @@ import com.intellij.debugger.DebuggerInvocationUtil; import com.intellij.debugger.DebuggerManagerEx; import com.intellij.debugger.engine.ContextUtil; import com.intellij.debugger.engine.evaluation.*; -import com.intellij.debugger.engine.evaluation.expression.EvaluatorBuilderImpl; -import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator; -import com.intellij.debugger.engine.evaluation.expression.Modifier; +import com.intellij.debugger.engine.evaluation.expression.*; import com.intellij.debugger.engine.events.DebuggerContextCommandImpl; import com.intellij.debugger.engine.events.SuspendContextCommandImpl; import com.intellij.debugger.impl.*; @@ -225,6 +223,19 @@ public class SetValueAction extends DebuggerAction { value = context.getSuspendContext().getDebugProcess().getVirtualMachineProxy().mirrorOf((float)dValue); } } + if (value != null) { + if (varType instanceof PrimitiveType) { + if (!(value instanceof PrimitiveValue)) { + value = (Value)new UnBoxingEvaluator(new IdentityEvaluator(value)).evaluate(context); + } + } + else if (UnBoxingEvaluator.isTypeUnboxable(varType.name())) { + // variable is not primitive and boxing/unboxing is applicable + if (value instanceof PrimitiveValue) { + value = (Value)new BoxingEvaluator(new IdentityEvaluator(value)).evaluate(context); + } + } + } return value; } diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/BoxingEvaluator.java b/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/BoxingEvaluator.java new file mode 100644 index 0000000000..060bc902b4 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/BoxingEvaluator.java @@ -0,0 +1,98 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.debugger.engine.evaluation.expression; + +import com.intellij.debugger.engine.DebugProcessImpl; +import com.intellij.debugger.engine.JVMNameUtil; +import com.intellij.debugger.engine.evaluation.EvaluateException; +import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; +import com.sun.jdi.*; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Eugene Zhuravlev + * Date: Feb 8, 2010 + */ +public class BoxingEvaluator implements Evaluator{ + private final Evaluator myOperand; + + public BoxingEvaluator(Evaluator operand) { + myOperand = operand; + } + + public Object evaluate(EvaluationContextImpl context) throws EvaluateException { + final Object result = myOperand.evaluate(context); + if (result == null || result instanceof ObjectReference) { + return result; + } + + if (result instanceof BooleanValue) { + return convertToWrapper(context, (BooleanValue)result, "java.lang.Boolean"); + } + if (result instanceof ByteValue) { + return convertToWrapper(context, (ByteValue)result, "java.lang.Byte"); + } + if (result instanceof CharValue) { + return convertToWrapper(context, (CharValue)result, "java.lang.Character"); + } + if (result instanceof ShortValue) { + return convertToWrapper(context, (ShortValue)result, "java.lang.Short"); + } + if (result instanceof IntegerValue) { + return convertToWrapper(context, (IntegerValue)result, "java.lang.Integer"); + } + if (result instanceof LongValue) { + return convertToWrapper(context, (LongValue)result, "java.lang.Long"); + } + if (result instanceof FloatValue) { + return convertToWrapper(context, (FloatValue)result, "java.lang.Float"); + } + if (result instanceof DoubleValue) { + return convertToWrapper(context, (DoubleValue)result, "java.lang.Double"); + } + throw new EvaluateException("Cannot perform boxing conversion for a value of type " + ((Value)result).type().name()); + } + + @Nullable + public Modifier getModifier() { + return null; + } + + private static Value convertToWrapper(EvaluationContextImpl context, PrimitiveValue value, String wrapperTypeName) throws + EvaluateException { + final DebugProcessImpl process = context.getDebugProcess(); + final ClassType wrapperClass = (ClassType)process.findClass(context, wrapperTypeName, null); + final String methodSignature = "(" + JVMNameUtil.getPrimitiveSignature(value.type().name()) + ")L" + wrapperTypeName.replace('.', '/') + ";"; + + List methods = wrapperClass.methodsByName("valueOf", methodSignature); + if (methods.size() == 0) { // older JDK version + methods = wrapperClass.methodsByName("", methodSignature); + } + if (methods.size() == 0) { + throw new EvaluateException("Cannot construct wrapper object for value of type " + value.type() + ": Unable to find either valueOf() or constructor method"); + } + + final Method factoryMethod = methods.get(0); + + final ArrayList args = new ArrayList(); + args.add(value); + + return process.invokeMethod(context, wrapperClass, factoryMethod, args); + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/EvaluatorBuilderImpl.java b/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/EvaluatorBuilderImpl.java index 0e1f779e48..f3f1b3d9ba 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/EvaluatorBuilderImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/EvaluatorBuilderImpl.java @@ -20,6 +20,8 @@ */ package com.intellij.debugger.engine.evaluation.expression; +import com.intellij.codeInsight.daemon.JavaErrorMessages; +import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil; import com.intellij.debugger.DebuggerBundle; import com.intellij.debugger.SourcePosition; import com.intellij.debugger.engine.ContextUtil; @@ -118,31 +120,40 @@ public class EvaluatorBuilderImpl implements EvaluatorBuilder { @Override public void visitAssignmentExpression(PsiAssignmentExpression expression) { - PsiExpression rExpression = expression.getRExpression(); - if(rExpression == null) throw new EvaluateRuntimeException(EvaluateExceptionUtil - .createEvaluateException(DebuggerBundle.message("evaluation.error.invalid.expression", expression.getText()))); + final PsiExpression rExpression = expression.getRExpression(); + if(rExpression == null) { + throw new EvaluateRuntimeException( + EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.invalid.expression", expression.getText())) + ); + } rExpression.accept(this); Evaluator rEvaluator = myResult; if(expression.getOperationSign().getTokenType() != JavaTokenType.EQ) { - throw new EvaluateRuntimeException(EvaluateExceptionUtil.createEvaluateException( - DebuggerBundle.message("evaluation.error.operation.not.supported", expression.getOperationSign().getText()))); + throw new EvaluateRuntimeException( + EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.operation.not.supported", expression.getOperationSign().getText())) + ); } - PsiExpression lExpression = expression.getLExpression(); + final PsiExpression lExpression = expression.getLExpression(); - if(lExpression.getType() == null) { - throw new EvaluateRuntimeException(EvaluateExceptionUtil - .createEvaluateException(DebuggerBundle.message("evaluation.error.unknown.expression.type", lExpression.getText()))); + final PsiType lType = lExpression.getType(); + if(lType == null) { + throw new EvaluateRuntimeException( + EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.unknown.expression.type", lExpression.getText())) + ); } - if(!TypeConversionUtil.areTypesAssignmentCompatible(lExpression.getType(), rExpression)) { + if(!TypeConversionUtil.areTypesAssignmentCompatible(lType, rExpression)) { throw new EvaluateRuntimeException(EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.incompatible.types", expression.getOperationSign().getText()))); } lExpression.accept(this); Evaluator lEvaluator = myResult; + if (TypeConversionUtil.boxingConversionApplicable(lType, rExpression.getType())) { + rEvaluator = (lType instanceof PsiPrimitiveType)? new UnBoxingEvaluator(rEvaluator) : new BoxingEvaluator(rEvaluator); + } myResult = new AssignmentEvaluator(lEvaluator, rEvaluator); } @@ -266,9 +277,10 @@ public class EvaluatorBuilderImpl implements EvaluatorBuilder { if (LOG.isDebugEnabled()) { LOG.debug("visitBinaryExpression " + expression); } - expression.getLOperand().accept(this); + final PsiExpression lOperand = expression.getLOperand(); + lOperand.accept(this); Evaluator lResult = myResult; - PsiExpression rOperand = expression.getROperand(); + final PsiExpression rOperand = expression.getROperand(); if(rOperand == null) { throw new EvaluateRuntimeException(EvaluateExceptionUtil .createEvaluateException(DebuggerBundle.message("evaluation.error.invalid.expression", expression.getText()))); @@ -280,6 +292,18 @@ public class EvaluatorBuilderImpl implements EvaluatorBuilder { throw new EvaluateRuntimeException(EvaluateExceptionUtil .createEvaluateException(DebuggerBundle.message("evaluation.error.unknown.expression.type", expression.getText()))); } + // handle unboxing if neccesary + final PsiType lType = lOperand.getType(); + final PsiType rType = rOperand.getType(); + if (TypeConversionUtil.boxingConversionApplicable(lType, rType)) { + if (lType instanceof PsiPrimitiveType) { + myResult = new UnBoxingEvaluator(myResult); + } + else if (rType instanceof PsiPrimitiveType) { + lResult = new UnBoxingEvaluator(lResult); + } + } + myResult = new BinaryExpressionEvaluator(lResult, myResult, opType, type.getCanonicalText()); } @@ -291,13 +315,13 @@ public class EvaluatorBuilderImpl implements EvaluatorBuilder { for (PsiElement declaredElement : declaredElements) { if (declaredElement instanceof PsiLocalVariable) { if (myCurrentFragmentEvaluator != null) { - PsiLocalVariable localVariable = ((PsiLocalVariable)declaredElement); + final PsiLocalVariable localVariable = ((PsiLocalVariable)declaredElement); - PsiType type = localVariable.getType(); + final PsiType lType = localVariable.getType(); PsiElementFactory elementFactory = JavaPsiFacade.getInstance(localVariable.getProject()).getElementFactory(); try { - PsiExpression initialValue = elementFactory.createExpressionFromText(PsiTypesUtil.getDefaultValueOfType(type), null); + PsiExpression initialValue = elementFactory.createExpressionFromText(PsiTypesUtil.getDefaultValueOfType(lType), null); Object value = JavaConstantExpressionEvaluator.computeConstantExpression(initialValue, true); myCurrentFragmentEvaluator.setInitialValue(localVariable.getName(), value); } @@ -315,6 +339,7 @@ public class EvaluatorBuilderImpl implements EvaluatorBuilder { throw new EvaluateRuntimeException(EvaluateExceptionUtil.createEvaluateException( DebuggerBundle.message("evaluation.error.incompatible.variable.initializer.type", localVariable.getName()))); } + final PsiType rType = initializer.getType(); initializer.accept(this); Evaluator rEvaluator = myResult; @@ -323,7 +348,16 @@ public class EvaluatorBuilderImpl implements EvaluatorBuilder { localVarReference.accept(this); Evaluator lEvaluator = myResult; - evaluators.add(new AssignmentEvaluator(lEvaluator, rEvaluator)); + Evaluator assignment = new AssignmentEvaluator(lEvaluator, rEvaluator); + if (TypeConversionUtil.boxingConversionApplicable(lType, rType)) { + if (lType instanceof PsiPrimitiveType) { + assignment = new UnBoxingEvaluator(assignment); + } + else { + assignment = new BoxingEvaluator(assignment); + } + } + evaluators.add(assignment); } catch (IncorrectOperationException e) { LOG.error(e); @@ -602,8 +636,8 @@ public class EvaluatorBuilderImpl implements EvaluatorBuilder { @Override public void visitPrefixExpression(final PsiPrefixExpression expression) { - final PsiType type = expression.getType(); - if(type == null) { + final PsiType expressionType = expression.getType(); + if(expressionType == null) { throw new EvaluateRuntimeException( EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.unknown.expression.type", expression.getText())) ); @@ -616,6 +650,8 @@ public class EvaluatorBuilderImpl implements EvaluatorBuilder { ); } + final PsiType operandExpressionType = operandExpression.getType(); + operandExpression.accept(this); final Evaluator operand = myResult; @@ -628,10 +664,22 @@ public class EvaluatorBuilderImpl implements EvaluatorBuilder { PsiElementFactory elementFactory = JavaPsiFacade.getInstance(expression.getProject()).getElementFactory(); PsiExpression one = elementFactory.createExpressionFromText("1", null); one.accept(this); + // handle unboxing issues + Evaluator left = operand; + if (!(operandExpressionType instanceof PsiPrimitiveType)) { + left = new UnBoxingEvaluator(left); + } + PsiType expected = expressionType; + final PsiPrimitiveType unboxedExpectedType = PsiPrimitiveType.getUnboxedType(expected); + if (unboxedExpectedType != null) { + expected = unboxedExpectedType; + } + final BinaryExpressionEvaluator rightEval = new BinaryExpressionEvaluator( + left, myResult, isPlus ? JavaTokenType.PLUS : JavaTokenType.MINUS, expected.getCanonicalText() + ); myResult = new AssignmentEvaluator( - operand, - new BinaryExpressionEvaluator(operand, myResult, isPlus ? JavaTokenType.PLUS : JavaTokenType.MINUS, type.getCanonicalText()) + operand, unboxedExpectedType != null? new BoxingEvaluator(rightEval) : rightEval ); } catch (IncorrectOperationException e) { @@ -639,7 +687,15 @@ public class EvaluatorBuilderImpl implements EvaluatorBuilder { } } else { - myResult = new UnaryExpressionEvaluator(opType, type.getCanonicalText(), operand, expression.getOperationSign().getText()); + PsiType expected = expressionType; + final PsiPrimitiveType unboxedExpectedType = PsiPrimitiveType.getUnboxedType(expected); + if (unboxedExpectedType != null) { + expected = unboxedExpectedType; + } + final UnaryExpressionEvaluator unaryEvaluator = + new UnaryExpressionEvaluator(opType, expected.getCanonicalText(), new UnBoxingEvaluator(operand), + expression.getOperationSign().getText()); + myResult = unboxedExpectedType != null? new BoxingEvaluator(unaryEvaluator) : unaryEvaluator; } } @@ -768,11 +824,42 @@ public class EvaluatorBuilderImpl implements EvaluatorBuilder { myResult = new ArrayAccessEvaluator(arrayEvaluator, indexEvaluator); } + @SuppressWarnings({"ConstantConditions"}) @Override public void visitTypeCastExpression(PsiTypeCastExpression expression) { - expression.getOperand().accept(this); - PsiType castType = expression.getCastType().getType(); - myResult = new TypeCastEvaluator(myResult, castType.getCanonicalText(), castType instanceof PsiPrimitiveType); + final PsiExpression operandExpr = expression.getOperand(); + operandExpr.accept(this); + Evaluator operandEvaluator = myResult; + final PsiType castType = expression.getCastType().getType(); + final PsiType operandType = operandExpr.getType(); + + if (!TypeConversionUtil.areTypesConvertible(operandType, castType)) { + throw new EvaluateRuntimeException( + new EvaluateException(JavaErrorMessages.message("inconvertible.type.cast", HighlightUtil.formatType(operandType), HighlightUtil.formatType(castType))) + ); + } + + final boolean shouldPerformBoxingConversion = TypeConversionUtil.boxingConversionApplicable(castType, operandType); + final boolean castingToPrimitive = castType instanceof PsiPrimitiveType; + if (shouldPerformBoxingConversion && castingToPrimitive) { + operandEvaluator = new UnBoxingEvaluator(operandEvaluator); + } + + final boolean performCastToWrapperClass = shouldPerformBoxingConversion && !castingToPrimitive; + + String castTypeName = castType.getCanonicalText(); + if (performCastToWrapperClass) { + final PsiPrimitiveType unboxedType = PsiPrimitiveType.getUnboxedType(castType); + if (unboxedType != null) { + castTypeName = unboxedType.getCanonicalText(); + } + } + + myResult = new TypeCastEvaluator(operandEvaluator, castTypeName, castingToPrimitive); + + if (performCastToWrapperClass) { + myResult = new BoxingEvaluator(myResult); + } } @Override diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/IdentityEvaluator.java b/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/IdentityEvaluator.java new file mode 100644 index 0000000000..4ff1a851c1 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/IdentityEvaluator.java @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.debugger.engine.evaluation.expression; + +import com.intellij.debugger.engine.evaluation.EvaluateException; +import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; +import com.sun.jdi.Value; + +/** +* @author Eugene Zhuravlev +* Date: Feb 9, 2010 +*/ +public class IdentityEvaluator implements Evaluator { + private final Value myValue; + + public IdentityEvaluator(Value value) { + myValue = value; + } + + public Object evaluate(EvaluationContextImpl context) throws EvaluateException { + return myValue; + } + + public Modifier getModifier() { + return null; + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/UnBoxingEvaluator.java b/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/UnBoxingEvaluator.java new file mode 100644 index 0000000000..faa963756f --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/UnBoxingEvaluator.java @@ -0,0 +1,90 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.debugger.engine.evaluation.expression; + +import com.intellij.debugger.engine.DebugProcessImpl; +import com.intellij.debugger.engine.evaluation.EvaluateException; +import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; +import com.intellij.openapi.util.Pair; +import com.intellij.util.containers.HashMap; +import com.sun.jdi.ClassType; +import com.sun.jdi.Method; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.Value; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author Eugene Zhuravlev + * Date: Feb 8, 2010 + */ +public class UnBoxingEvaluator implements Evaluator{ + private final Evaluator myOperand; + private static final Map> TYPES_TO_CONVERSION_METHOD_MAP = new HashMap>(); + static { + TYPES_TO_CONVERSION_METHOD_MAP.put("java.lang.Boolean", new Pair("booleanValue", "()Z")); + TYPES_TO_CONVERSION_METHOD_MAP.put("java.lang.Byte", new Pair("byteValue", "()B")); + TYPES_TO_CONVERSION_METHOD_MAP.put("java.lang.Character", new Pair("charValue", "()C")); + TYPES_TO_CONVERSION_METHOD_MAP.put("java.lang.Short", new Pair("shortValue", "()S")); + TYPES_TO_CONVERSION_METHOD_MAP.put("java.lang.Integer", new Pair("intValue", "()I")); + TYPES_TO_CONVERSION_METHOD_MAP.put("java.lang.Long", new Pair("longValue", "()J")); + TYPES_TO_CONVERSION_METHOD_MAP.put("java.lang.Float", new Pair("floatValue", "()F")); + TYPES_TO_CONVERSION_METHOD_MAP.put("java.lang.Double", new Pair("doubleValue", "()D")); + } + + public static boolean isTypeUnboxable(String typeName) { + return TYPES_TO_CONVERSION_METHOD_MAP.containsKey(typeName); + } + + public UnBoxingEvaluator(Evaluator operand) { + myOperand = operand; + } + + public Object evaluate(EvaluationContextImpl context) throws EvaluateException { + final Value result = (Value)myOperand.evaluate(context); + if (result instanceof ObjectReference) { + final String valueTypeName = result.type().name(); + final Pair pair = TYPES_TO_CONVERSION_METHOD_MAP.get(valueTypeName); + if (pair != null) { + return convertToPrimitive(context, (ObjectReference)result, pair.getFirst(), pair.getSecond()); + } + } + return result; + } + + @Nullable + public Modifier getModifier() { + return null; + } + + private static Value convertToPrimitive(EvaluationContextImpl context, ObjectReference value, final String conversionMethodName, + String conversionMethodSignature) throws EvaluateException { + final DebugProcessImpl process = context.getDebugProcess(); + final ClassType wrapperClass = (ClassType)value.referenceType(); + final List methods = wrapperClass.methodsByName(conversionMethodName, conversionMethodSignature); + if (methods.size() == 0) { + throw new EvaluateException("Cannot convert to primitive value of type " + value.type() + ": Unable to find method " + + conversionMethodName + conversionMethodSignature); + } + + final Method method = methods.get(0); + + return process.invokeMethod(context, value, method, new ArrayList()); + } +} \ No newline at end of file -- 2.11.4.GIT