Fixes issue 2446. Autoboxing was not handled correctly when a box type was used with ++, --, or
a compound assignment such as += . This patch leaves alone GenerateJavaAST but adds
a FixAssignmentToUnbox normalization to handle these trees. FixAssignmentToUnbox
first breaks apart compound assignments using CompoundAssignmentNormalizer and then fixes up
the resulting erroneous assignment statements, all of which are of the form "unbox(x) = expression".
Additionally, the JJS JTypeOracle is updated so that before the first call to setInstantiatedTypes(), all
types are considered instantiated.
Review by: scottb
git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/1.5@3073 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index 95a6d43..aaadac7 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -38,10 +38,10 @@
import com.google.gwt.dev.jjs.impl.BuildTypeMap;
import com.google.gwt.dev.jjs.impl.CastNormalizer;
import com.google.gwt.dev.jjs.impl.CatchBlockNormalizer;
-import com.google.gwt.dev.jjs.impl.CompoundAssignmentNormalizer;
import com.google.gwt.dev.jjs.impl.DeadCodeElimination;
import com.google.gwt.dev.jjs.impl.EqualityNormalizer;
import com.google.gwt.dev.jjs.impl.Finalizer;
+import com.google.gwt.dev.jjs.impl.FixAssignmentToUnbox;
import com.google.gwt.dev.jjs.impl.GenerateJavaAST;
import com.google.gwt.dev.jjs.impl.GenerateJavaScriptAST;
import com.google.gwt.dev.jjs.impl.JavaScriptObjectNormalizer;
@@ -51,6 +51,7 @@
import com.google.gwt.dev.jjs.impl.MakeCallsStatic;
import com.google.gwt.dev.jjs.impl.MethodCallTightener;
import com.google.gwt.dev.jjs.impl.MethodInliner;
+import com.google.gwt.dev.jjs.impl.PostOptimizationCompoundAssignmentNormalizer;
import com.google.gwt.dev.jjs.impl.Pruner;
import com.google.gwt.dev.jjs.impl.ReplaceRebinds;
import com.google.gwt.dev.jjs.impl.TypeMap;
@@ -332,6 +333,8 @@
// (3) Perform Java AST normalizations.
+ FixAssignmentToUnbox.exec(jprogram);
+
/*
* TODO: If we defer this until later, we could maybe use the results of
* the assertions to enable more optimizations.
@@ -400,7 +403,7 @@
LongCastNormalizer.exec(jprogram);
JsoDevirtualizer.exec(jprogram);
CatchBlockNormalizer.exec(jprogram);
- CompoundAssignmentNormalizer.exec(jprogram);
+ PostOptimizationCompoundAssignmentNormalizer.exec(jprogram);
LongEmulationNormalizer.exec(jprogram);
CastNormalizer.exec(jprogram);
ArrayNormalizer.exec(jprogram);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
index 85804c3..64db362 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
@@ -88,7 +88,7 @@
hasLiveCode = true;
return false;
}
-
+
@Override
public boolean visit(JExpressionStatement x, Context ctx) {
JExpression expr = x.getExpr();
@@ -144,7 +144,7 @@
private final Map<JClassType, Set<JInterfaceType>> implementsMap = new IdentityHashMap<JClassType, Set<JInterfaceType>>();
- private final Set<JReferenceType> instantiatedTypes = new HashSet<JReferenceType>();
+ private Set<JReferenceType> instantiatedTypes = null;
private final Map<JInterfaceType, Set<JClassType>> isImplementedMap = new IdentityHashMap<JInterfaceType, Set<JClassType>>();
@@ -389,10 +389,6 @@
return results;
}
- public Set<JReferenceType> getInstantiatedTypes() {
- return instantiatedTypes;
- }
-
public boolean hasClinit(JReferenceType type) {
return hasClinitSet.contains(type);
}
@@ -406,6 +402,11 @@
}
public boolean isInstantiatedType(JReferenceType type) {
+ if (instantiatedTypes == null) {
+ // The instantiated types have not yet been computed.
+ return true;
+ }
+
if (type instanceof JNullType) {
return true;
}
@@ -443,7 +444,7 @@
}
public void setInstantiatedTypes(Set<JReferenceType> instantiatedTypes) {
- this.instantiatedTypes.clear();
+ this.instantiatedTypes = new HashSet<JReferenceType>();
this.instantiatedTypes.addAll(instantiatedTypes);
}
@@ -614,7 +615,7 @@
Map<JClassType, Set<JMethod>> overrideMap = getOrCreateMap(virtualUpRefMap,
method);
for (JClassType classType : overrideMap.keySet()) {
- if (instantiatedTypes.contains(classType)) {
+ if (isInstantiatedType(classType)) {
Set<JMethod> set = overrideMap.get(classType);
results.addAll(set);
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/AutoboxUtils.java b/dev/core/src/com/google/gwt/dev/jjs/impl/AutoboxUtils.java
new file mode 100644
index 0000000..b9caf67
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/AutoboxUtils.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * 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.google.gwt.dev.jjs.impl;
+
+import com.google.gwt.dev.jjs.InternalCompilerException;
+import com.google.gwt.dev.jjs.ast.JClassType;
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JMethodCall;
+import com.google.gwt.dev.jjs.ast.JParameter;
+import com.google.gwt.dev.jjs.ast.JPrimitiveType;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.ast.JReferenceType;
+
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Utilities for managing autoboxing of Java primitive types.
+ */
+public class AutoboxUtils {
+ private Set<JPrimitiveType> boxablePrimitiveTypes;
+ private Map<JClassType, JPrimitiveType> boxClassToPrimitiveMap;
+ private Set<JReferenceType> boxTypes;
+ private final JProgram program;
+ private Set<JMethod> unboxMethods;
+
+ public AutoboxUtils(JProgram program) {
+ this.program = program;
+ computeBoxablePrimitiveTypes();
+ computeBoxClassToPrimitiveMap();
+ computeBoxTypes();
+ computeUnboxMethods();
+ }
+
+ public JExpression box(JExpression toBox, JClassType wrapperType) {
+ return box(toBox, primitiveTypeForBoxClass(wrapperType), wrapperType);
+ }
+
+ public JExpression box(JExpression toBox, JPrimitiveType primitiveType) {
+ // Find the wrapper type for this primitive type.
+ String wrapperTypeName = primitiveType.getWrapperTypeName();
+ JClassType wrapperType = (JClassType) program.getFromTypeMap(wrapperTypeName);
+ if (wrapperType == null) {
+ throw new InternalCompilerException(toBox, "Cannot find wrapper type '"
+ + wrapperTypeName + "' associated with primitive type '"
+ + primitiveType.getName() + "'", null);
+ }
+
+ return box(toBox, primitiveType, wrapperType);
+ }
+
+ public JClassType boxClassForPrimitive(JPrimitiveType prim) {
+ return (JClassType) program.getFromTypeMap(prim.getWrapperTypeName());
+ }
+
+ /**
+ * If <code>x</code> is an unbox expression, then return the expression that
+ * is being unboxed by it. Otherwise, return <code>null</code>.
+ */
+ public JExpression undoUnbox(JExpression arg) {
+ if (arg instanceof JMethodCall) {
+ JMethodCall argMethodCall = (JMethodCall) arg;
+ if (unboxMethods.contains(argMethodCall.getTarget())) {
+ return argMethodCall.getInstance();
+ }
+ }
+ return null;
+ }
+
+ private JExpression box(JExpression toBox, JPrimitiveType primitiveType,
+ JClassType wrapperType) {
+ // Find the correct valueOf() method.
+ JMethod valueOfMethod = null;
+ for (JMethod method : wrapperType.methods) {
+ if ("valueOf".equals(method.getName())) {
+ if (method.params.size() == 1) {
+ JParameter param = method.params.get(0);
+ if (param.getType() == primitiveType) {
+ // Found it.
+ valueOfMethod = method;
+ break;
+ }
+ }
+ }
+ }
+
+ if (valueOfMethod == null || !valueOfMethod.isStatic()
+ || valueOfMethod.getType() != wrapperType) {
+ throw new InternalCompilerException(toBox,
+ "Expected to find a method on '" + wrapperType.getName()
+ + "' whose signature matches 'public static "
+ + wrapperType.getName() + " valueOf(" + primitiveType.getName()
+ + ")'", null);
+ }
+
+ // Create the boxing call.
+ JMethodCall call = new JMethodCall(program, toBox.getSourceInfo(), null,
+ valueOfMethod);
+ call.getArgs().add(toBox);
+ return call;
+ }
+
+ private void computeBoxablePrimitiveTypes() {
+ boxablePrimitiveTypes = new LinkedHashSet<JPrimitiveType>();
+ boxablePrimitiveTypes.add(program.getTypePrimitiveBoolean());
+ boxablePrimitiveTypes.add(program.getTypePrimitiveByte());
+ boxablePrimitiveTypes.add(program.getTypePrimitiveChar());
+ boxablePrimitiveTypes.add(program.getTypePrimitiveShort());
+ boxablePrimitiveTypes.add(program.getTypePrimitiveInt());
+ boxablePrimitiveTypes.add(program.getTypePrimitiveLong());
+ boxablePrimitiveTypes.add(program.getTypePrimitiveFloat());
+ boxablePrimitiveTypes.add(program.getTypePrimitiveDouble());
+ }
+
+ private void computeBoxClassToPrimitiveMap() {
+ boxClassToPrimitiveMap = new LinkedHashMap<JClassType, JPrimitiveType>();
+ for (JPrimitiveType prim : boxablePrimitiveTypes) {
+ boxClassToPrimitiveMap.put(boxClassForPrimitive(prim), prim);
+ }
+ }
+
+ private void computeBoxTypes() {
+ boxTypes = new LinkedHashSet<JReferenceType>();
+ for (JPrimitiveType prim : boxablePrimitiveTypes) {
+ boxTypes.add(boxClassForPrimitive(prim));
+ }
+ }
+
+ private void computeUnboxMethods() {
+ unboxMethods = new LinkedHashSet<JMethod>();
+ for (JReferenceType boxType : boxTypes) {
+ for (JMethod method : boxType.methods) {
+ if (!method.isStatic() && method.params.isEmpty()
+ && method.getName().endsWith("Value")
+ && (method.getType() instanceof JPrimitiveType)) {
+ unboxMethods.add(method);
+ }
+ }
+ }
+ }
+
+ private JPrimitiveType primitiveTypeForBoxClass(JClassType wrapperType) {
+ JPrimitiveType primitiveType = boxClassToPrimitiveMap.get(wrapperType);
+ if (primitiveType == null) {
+ throw new IllegalArgumentException("Not a box class: " + wrapperType);
+ }
+ return primitiveType;
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
index 1ef357d..4ec5616 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
@@ -37,13 +37,35 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Stack;
/**
- * Replace any complex assignments that will cause problems down the road with
- * broken expressions; replace side-effect expressions in the lhs with temps to
- * prevent multiple evaluation.
+ * <p>
+ * Replace problematic compound assignments with a sequence of simpler
+ * operations, all of which are either simple assignments or are non-assigning
+ * operations. When doing so, be careful that side effects happen exactly once
+ * and that the order of any side effects is preserved. The choice of which
+ * assignments to replace is made in subclasses; they must override the three
+ * <code>shouldBreakUp()</code> methods.
+ * </p>
+ *
+ * <p>
+ * Note that because AST nodes are mutable, they cannot be reused in different
+ * parts of the same tree. Instead, the node must be cloned before each
+ * insertion into a tree other than the first.
+ * </p>
+ *
+ * <p>
+ * If the <code>reuseTemps</code> constructor parameter is set to
+ * <code>false</code>, then temps will be reused aggressively, even when
+ * their types do not match the values assigned to them. To determine when a
+ * temp can be reused, the current implementation uses a notion of "temp usage
+ * scopes". Every time a temporary variable is allocated, it is recorded in the
+ * current temp usage scope. Once the current temp usage scope is exited, all of
+ * its temps become available for use for other purposes.
+ * </p>
*/
-public class CompoundAssignmentNormalizer {
+public abstract class CompoundAssignmentNormalizer {
/**
* Breaks apart certain complex assignments.
@@ -56,18 +78,7 @@
if (op.getNonAssignmentOf() == null) {
return;
}
-
- boolean doIt = false;
- if (x.getType() == program.getTypePrimitiveLong()) {
- doIt = true;
- }
- if (op == JBinaryOperator.ASG_DIV
- && x.getType() != program.getTypePrimitiveFloat()
- && x.getType() != program.getTypePrimitiveDouble()) {
- doIt = true;
- }
-
- if (!doIt) {
+ if (!shouldBreakUp(x)) {
return;
}
@@ -77,17 +88,19 @@
* expressions that could have side effects with temporaries, so that they
* are only run once.
*/
- final int pushLocalIndex = localIndex;
+ enterTempUsageScope();
ReplaceSideEffectsInLvalue replacer = new ReplaceSideEffectsInLvalue(
new JMultiExpression(program, x.getSourceInfo()));
JExpression newLhs = replacer.accept(x.getLhs());
- localIndex = pushLocalIndex;
+ exitTempUsageScope();
JBinaryOperation operation = new JBinaryOperation(program,
x.getSourceInfo(), newLhs.getType(), op.getNonAssignmentOf(), newLhs,
x.getRhs());
+ // newLhs is cloned below because it was used in operation
JBinaryOperation asg = new JBinaryOperation(program, x.getSourceInfo(),
- newLhs.getType(), JBinaryOperator.ASG, newLhs, operation);
+ newLhs.getType(), JBinaryOperator.ASG,
+ cloner.cloneExpression(newLhs), operation);
JMultiExpression multiExpr = replacer.getMultiExpr();
if (multiExpr.exprs.isEmpty()) {
@@ -112,7 +125,7 @@
if (!op.isModifying()) {
return;
}
- if (x.getType() != program.getTypePrimitiveLong()) {
+ if (!shouldBreakUp(x)) {
return;
}
@@ -120,19 +133,21 @@
// (t = x, x += 1, t)
// First, replace the arg with a non-side-effect causing one.
- final int pushLocalIndex = localIndex;
+ enterTempUsageScope();
JMultiExpression multi = new JMultiExpression(program, x.getSourceInfo());
ReplaceSideEffectsInLvalue replacer = new ReplaceSideEffectsInLvalue(
multi);
JExpression newArg = replacer.accept(x.getArg());
+ JExpression expressionReturn = expressionToReturn(newArg);
+
// Now generate the appropriate expressions.
- JLocal tempLocal = getTempLocal(newArg.getType());
+ JLocal tempLocal = getTempLocal(expressionReturn.getType());
// t = x
JLocalRef tempRef = new JLocalRef(program, x.getSourceInfo(), tempLocal);
JBinaryOperation asg = new JBinaryOperation(program, x.getSourceInfo(),
- x.getType(), JBinaryOperator.ASG, tempRef, newArg);
+ x.getType(), JBinaryOperator.ASG, tempRef, expressionReturn);
multi.exprs.add(asg);
// x += 1
@@ -145,7 +160,7 @@
multi.exprs.add(tempRef);
ctx.replaceMe(multi);
- localIndex = pushLocalIndex;
+ exitTempUsageScope();
}
@Override
@@ -154,7 +169,7 @@
if (!op.isModifying()) {
return;
}
- if (x.getType() != program.getTypePrimitiveLong()) {
+ if (!shouldBreakUp(x)) {
return;
}
@@ -186,11 +201,22 @@
+ String.valueOf(op.getSymbol()));
}
+ JExpression one;
+ if (arg.getType() == program.getTypePrimitiveLong()) {
+ // use an explicit long, so that LongEmulationNormalizer does not get
+ // confused
+ one = program.getLiteralLong(1);
+ } else {
+ // int is safe to add to all other types
+ one = program.getLiteralInt(1);
+ }
+ // arg is cloned below because the caller is allowed to use it somewhere
JBinaryOperation asg = new JBinaryOperation(program, arg.getSourceInfo(),
- arg.getType(), newOp, arg, program.getLiteralLong(1));
+ arg.getType(), newOp, cloner.cloneExpression(arg), one);
return asg;
}
}
+
/**
* Replaces side effects in lvalue.
*/
@@ -260,41 +286,75 @@
x.getType(), JBinaryOperator.ASG, tempRef, x);
multi.exprs.add(asg);
// Update me with the temp
- return tempRef;
+ return cloner.cloneExpression(tempRef);
}
}
- public static void exec(JProgram program) {
- new CompoundAssignmentNormalizer(program).execImpl();
- }
+ private static int localCounter;
+ protected final JProgram program;
+ private final CloneExpressionVisitor cloner;
private JMethodBody currentMethodBody;
- private int localIndex;
- private final JProgram program;
- private final List<JLocal> tempLocals = new ArrayList<JLocal>();
- private CompoundAssignmentNormalizer(JProgram program) {
+ private final boolean reuseTemps;
+ private List<JLocal> tempLocals;
+ private int tempLocalsIndex;
+ private Stack<Integer> usageScopeStarts;
+
+ protected CompoundAssignmentNormalizer(JProgram program, boolean reuseTemps) {
this.program = program;
+ this.reuseTemps = reuseTemps;
+ cloner = new CloneExpressionVisitor(program);
+ clearLocals();
}
- private void clearLocals() {
- tempLocals.clear();
- localIndex = 0;
- }
-
- private void execImpl() {
+ public void breakUpAssignments() {
BreakupAssignOpsVisitor breaker = new BreakupAssignOpsVisitor();
breaker.accept(program);
}
+ /**
+ * Decide what expression to return when breaking up a compound assignment of
+ * the form <code>lhs op= rhs</code>. By default the <code>lhs</code> is
+ * returned.
+ */
+ protected JExpression expressionToReturn(JExpression lhs) {
+ return lhs;
+ }
+
+ protected abstract boolean shouldBreakUp(JBinaryOperation x);
+
+ protected abstract boolean shouldBreakUp(JPostfixOperation x);
+
+ protected abstract boolean shouldBreakUp(JPrefixOperation x);
+
+ private void clearLocals() {
+ tempLocals = new ArrayList<JLocal>();
+ tempLocalsIndex = 0;
+ usageScopeStarts = new Stack<Integer>();
+ }
+
+ private void enterTempUsageScope() {
+ usageScopeStarts.push(tempLocalsIndex);
+ }
+
+ private void exitTempUsageScope() {
+ tempLocalsIndex = usageScopeStarts.pop();
+ }
+
+ /**
+ * Allocate a temporary local variable.
+ */
private JLocal getTempLocal(JType type) {
- if (localIndex < tempLocals.size()) {
- return tempLocals.get(localIndex++);
+ if (reuseTemps) {
+ if (tempLocalsIndex < tempLocals.size()) {
+ return tempLocals.get(tempLocalsIndex++);
+ }
}
+
JLocal newTemp = program.createLocal(null,
- ("$t" + localIndex++).toCharArray(), type, false, currentMethodBody);
+ ("$t" + localCounter++).toCharArray(), type, false, currentMethodBody);
tempLocals.add(newTemp);
return newTemp;
}
-
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/FixAssignmentToUnbox.java b/dev/core/src/com/google/gwt/dev/jjs/impl/FixAssignmentToUnbox.java
new file mode 100644
index 0000000..a5f9d9d
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/FixAssignmentToUnbox.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * 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.google.gwt.dev.jjs.impl;
+
+import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JBinaryOperation;
+import com.google.gwt.dev.jjs.ast.JBinaryOperator;
+import com.google.gwt.dev.jjs.ast.JClassType;
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JModVisitor;
+import com.google.gwt.dev.jjs.ast.JPostfixOperation;
+import com.google.gwt.dev.jjs.ast.JPrefixOperation;
+import com.google.gwt.dev.jjs.ast.JProgram;
+
+/**
+ * Most autoboxing is handled by {@link GenerateJavaAST}. The only cases it
+ * does not handle are <code>++</code>, <code>--</code>, and compound
+ * assignment operations (<code>+=</code>, etc.) when applied to a boxed
+ * type. This class fixes such cases in two steps. First, an internal subclass
+ * of {@link CompoundAssignmentNormalizer} simplifies such expressions to a
+ * simple assignment expression. Second, this visitor replaces an assignment to
+ * an unboxing method (<code>unbox(x) = unbox(x) + 1</code>) with an
+ * assignment to the underlying box (<code>x = box(unbox(x) + 1)</code>).
+ */
+public class FixAssignmentToUnbox extends JModVisitor {
+ /**
+ * Normalize compound assignments where the lhs is an unbox operation.
+ */
+ private static class CompoundAssignmentToUnboxNormalizer extends
+ CompoundAssignmentNormalizer {
+ private final AutoboxUtils autoboxUtils;
+
+ protected CompoundAssignmentToUnboxNormalizer(JProgram program) {
+ super(program, false);
+ autoboxUtils = new AutoboxUtils(program);
+ }
+
+ /**
+ * If the lhs is an unbox operation, then return the box rather than the
+ * original value.
+ */
+ @Override
+ protected JExpression expressionToReturn(JExpression lhs) {
+ JExpression boxed = autoboxUtils.undoUnbox(lhs);
+ if (boxed != null) {
+ return boxed;
+ }
+ return lhs;
+ }
+
+ @Override
+ protected boolean shouldBreakUp(JBinaryOperation x) {
+ return isUnboxExpression(x.getLhs());
+ }
+
+ @Override
+ protected boolean shouldBreakUp(JPostfixOperation x) {
+ return isUnboxExpression(x.getArg());
+ }
+
+ @Override
+ protected boolean shouldBreakUp(JPrefixOperation x) {
+ return isUnboxExpression(x.getArg());
+ }
+
+ private boolean isUnboxExpression(JExpression x) {
+ return (autoboxUtils.undoUnbox(x) != null);
+ }
+ }
+
+ public static void exec(JProgram program) {
+ (new CompoundAssignmentToUnboxNormalizer(program)).breakUpAssignments();
+ (new FixAssignmentToUnbox(program)).accept(program);
+ }
+
+ private final AutoboxUtils autoboxUtils;
+ private final JProgram program;
+
+ private FixAssignmentToUnbox(JProgram program) {
+ this.program = program;
+ this.autoboxUtils = new AutoboxUtils(program);
+ }
+
+ @Override
+ public void endVisit(JBinaryOperation x, Context ctx) {
+ // unbox(x) = foo -> x = box(foo)
+
+ if (x.getOp() != JBinaryOperator.ASG) {
+ return;
+ }
+
+ JExpression boxed = autoboxUtils.undoUnbox(x.getLhs());
+ if (boxed == null) {
+ return;
+ }
+
+ JClassType boxedType = (JClassType) boxed.getType();
+
+ ctx.replaceMe(new JBinaryOperation(program, x.getSourceInfo(), boxedType,
+ JBinaryOperator.ASG, boxed, autoboxUtils.box(x.getRhs(), boxedType)));
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
index b7e0074..1415823 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
@@ -231,6 +231,8 @@
return ice;
}
+ private final AutoboxUtils autoboxUtils;
+
private JReferenceType currentClass;
private ClassScope currentClassScope;
@@ -260,6 +262,7 @@
this.typeMap = typeMap;
this.program = program;
this.enableAsserts = enableAsserts;
+ autoboxUtils = new AutoboxUtils(program);
}
public void processEnumType(JEnumType type) {
@@ -465,9 +468,12 @@
+ primitiveTypeToBox.getName(), null);
}
- result = box(result, (JPrimitiveType) primitiveTypeToBox);
+ result = autoboxUtils.box(result,
+ ((JPrimitiveType) primitiveTypeToBox));
} else if ((x.implicitConversion & TypeIds.UNBOXING) != 0) {
-
+ // This code can actually leave an unbox operation in
+ // an lvalue position, for example ++x.intValue().
+ // Such trees are cleaned up in FixAssignmentToUnbox.
JType typeToUnbox = (JType) typeMap.get(x.resolvedType);
if (!(typeToUnbox instanceof JClassType)) {
throw new InternalCompilerException(result,
@@ -1602,8 +1608,8 @@
// May need to box or unbox the element assignment.
if (x.elementVariableImplicitWidening != -1) {
if ((x.elementVariableImplicitWidening & TypeIds.BOXING) != 0) {
- elementDecl.initializer = box(elementDecl.initializer,
- (JPrimitiveType) elementDecl.initializer.getType());
+ elementDecl.initializer = autoboxUtils.box(elementDecl.initializer,
+ ((JPrimitiveType) elementDecl.initializer.getType()));
} else if ((x.elementVariableImplicitWidening & TypeIds.UNBOXING) != 0) {
elementDecl.initializer = unbox(elementDecl.initializer,
(JClassType) elementDecl.initializer.getType());
@@ -1923,47 +1929,6 @@
}
}
- private JExpression box(JExpression toBox, JPrimitiveType primitiveType) {
- // Find the wrapper type for this primitive type.
- String wrapperTypeName = primitiveType.getWrapperTypeName();
- JClassType wrapperType = (JClassType) program.getFromTypeMap(wrapperTypeName);
- if (wrapperType == null) {
- throw new InternalCompilerException(toBox, "Cannot find wrapper type '"
- + wrapperTypeName + "' associated with primitive type '"
- + primitiveType.getName() + "'", null);
- }
-
- // Find the correct valueOf() method.
- JMethod valueOfMethod = null;
- for (JMethod method : wrapperType.methods) {
- if ("valueOf".equals(method.getName())) {
- if (method.params.size() == 1) {
- JParameter param = method.params.get(0);
- if (param.getType() == primitiveType) {
- // Found it.
- valueOfMethod = method;
- break;
- }
- }
- }
- }
-
- if (valueOfMethod == null || !valueOfMethod.isStatic()
- || valueOfMethod.getType() != wrapperType) {
- throw new InternalCompilerException(toBox,
- "Expected to find a method on '" + wrapperType.getName()
- + "' whose signature matches 'public static "
- + wrapperType.getName() + " valueOf(" + primitiveType.getName()
- + ")'", null);
- }
-
- // Create the boxing call.
- JMethodCall call = new JMethodCall(program, toBox.getSourceInfo(), null,
- valueOfMethod);
- call.getArgs().add(toBox);
- return call;
- }
-
private JDeclarationStatement createDeclaration(SourceInfo info,
JLocal local, JExpression value) {
return new JDeclarationStatement(program, info, new JLocalRef(program,
@@ -2698,6 +2663,7 @@
this.jsniMethodMap = jsniMethodMap;
}
+ @Override
public void endVisit(JClassType x, Context ctx) {
currentClass = null;
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/PostOptimizationCompoundAssignmentNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/PostOptimizationCompoundAssignmentNormalizer.java
new file mode 100644
index 0000000..e67dd23
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/PostOptimizationCompoundAssignmentNormalizer.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * 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.google.gwt.dev.jjs.impl;
+
+import com.google.gwt.dev.jjs.ast.JBinaryOperation;
+import com.google.gwt.dev.jjs.ast.JBinaryOperator;
+import com.google.gwt.dev.jjs.ast.JPostfixOperation;
+import com.google.gwt.dev.jjs.ast.JPrefixOperation;
+import com.google.gwt.dev.jjs.ast.JProgram;
+
+/**
+ * Normalize compound assignments as needed after optimization. Integer division
+ * and operations on longs need to be broken up.
+ */
+public class PostOptimizationCompoundAssignmentNormalizer extends
+ CompoundAssignmentNormalizer {
+ public static void exec(JProgram program) {
+ new PostOptimizationCompoundAssignmentNormalizer(program).breakUpAssignments();
+ }
+
+ protected PostOptimizationCompoundAssignmentNormalizer(JProgram program) {
+ super(program, true);
+ }
+
+ @Override
+ protected boolean shouldBreakUp(JBinaryOperation x) {
+ if (x.getType() == program.getTypePrimitiveLong()) {
+ return true;
+ }
+ if (x.getOp() == JBinaryOperator.ASG_DIV
+ && x.getType() != program.getTypePrimitiveFloat()
+ && x.getType() != program.getTypePrimitiveDouble()) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected boolean shouldBreakUp(JPostfixOperation x) {
+ if (x.getType() == program.getTypePrimitiveLong()) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected boolean shouldBreakUp(JPrefixOperation x) {
+ if (x.getType() == program.getTypePrimitiveLong()) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/user/test/com/google/gwt/dev/jjs/test/AutoboxTest.java b/user/test/com/google/gwt/dev/jjs/test/AutoboxTest.java
index db1d304..ea9559e 100644
--- a/user/test/com/google/gwt/dev/jjs/test/AutoboxTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/AutoboxTest.java
@@ -54,6 +54,7 @@
private short unboxedShort = 6550;
+ @Override
public String getModuleName() {
return "com.google.gwt.dev.jjs.CompilerSuite";
}
@@ -109,6 +110,433 @@
assertSame(7L, 7L);
}
+ /**
+ * Tests operations like += and *= where the left-hand side is a boxed type.
+ */
+ public void testCompoundAssignmentsWithByte() {
+ {
+ Long long1 = 10L;
+ Long long2 = long1;
+ long2 += 5;
+ assertEquals(10L, (long) long1);
+ assertEquals(15L, (long) long2);
+ }
+ {
+ Byte b = (byte) 10;
+ Byte b2 = b;
+ b2 += 1;
+ assertEquals(10, (byte) b);
+ assertEquals(11, (byte) b2);
+ }
+ {
+ Byte b = (byte) 10;
+ Byte b2 = b;
+ b2 -= 1;
+ assertEquals(10, (byte) b);
+ assertEquals(9, (byte) b2);
+ }
+ {
+ Byte b = (byte) 10;
+ Byte b2 = b;
+ b2 *= 2;
+ assertEquals(10, (byte) b);
+ assertEquals(20, (byte) b2);
+ }
+ {
+ Byte b = (byte) 10;
+ Byte b2 = b;
+ b2 /= 2;
+ assertEquals(10, (byte) b);
+ assertEquals(5, (byte) b2);
+ }
+ {
+ Byte b = (byte) 10;
+ Byte b2 = b;
+ b2 %= 3;
+ assertEquals(10, (byte) b);
+ assertEquals(1, (byte) b2);
+ }
+ {
+ Byte b = (byte) 10;
+ Byte b2 = b;
+ b2 <<= 1;
+ assertEquals(10, (byte) b);
+ assertEquals(20, (byte) b2);
+ }
+ {
+ Byte b = (byte) 10;
+ Byte b2 = b;
+ b2 >>= 1;
+ assertEquals(10, (byte) b);
+ assertEquals(5, (byte) b2);
+ }
+ {
+ Byte b = (byte) 10;
+ Byte b2 = b;
+ b2 >>>= 1;
+ assertEquals(10, (byte) b);
+ assertEquals(5, (byte) b2);
+ }
+ {
+ Byte b = (byte) 10;
+ Byte b2 = b;
+ b2 &= 8;
+ assertEquals(10, (byte) b);
+ assertEquals(8, (byte) b2);
+ }
+ {
+ Byte b = (byte) 10;
+ Byte b2 = b;
+ b2 |= 1;
+ assertEquals(10, (byte) b);
+ assertEquals(11, (byte) b2);
+ }
+ {
+ Byte b = (byte) 10;
+ Byte b2 = b;
+ b2 ^= 1;
+ assertEquals(10, (byte) b);
+ assertEquals(11, (byte) b2);
+ }
+ }
+
+ /**
+ * Test ++, --, and compound assignments like += when the left-hand side is a
+ * boxed Integer. Use assertNotSame to ensure that a new Integer is created
+ * instead of modifying the original integer in place. (Issue 2446).
+ */
+ public void testCompoundAssignmentsWithInteger() {
+ {
+ Integer operand, original, result;
+ original = operand = 0;
+ result = operand++;
+ // operand must be different object now.
+ assertNotSame("[o++] original != operand, ", original, operand);
+ assertSame("[o++] original == result, ", original, result);
+ assertNotSame("[o++] result != operand, ", result, operand);
+ // checks against boxedvalues cached object.
+ assertSame("[o++] valueOf(n) == operand, ", 1, operand);
+ // checks cached object's value.
+ assertEquals("[o++] n == operand.value, ", 1, operand.intValue());
+ }
+
+ {
+ Integer operand, original, result;
+ original = operand = 2;
+ result = ++operand;
+ assertNotSame("[++o] original != operand, ", original, operand);
+ assertNotSame("[++o] original != result, ", original, result);
+ assertSame("[++o] result == operand, ", result, operand);
+ assertSame("[++o] valueOf(n) == operand, ", 3, operand);
+ assertEquals("[++o] n == operand.value, ", 3, operand.intValue());
+ }
+
+ {
+ Integer operand, original, result;
+ original = operand = 5;
+ result = operand--;
+ assertNotSame("[o--] original != operand, ", original, operand);
+ assertSame("[o--] original == result, ", original, result);
+ assertNotSame("[o--] result != operand, ", result, operand);
+ assertSame("[o--] valueOf(n) == operand, ", 4, operand);
+ assertEquals("[o--] n == operand.value, ", 4, operand.intValue());
+ }
+
+ {
+ Integer operand, original, result;
+ original = operand = 7;
+ result = --operand;
+ assertNotSame("[--o] original != operand, ", original, operand);
+ assertNotSame("[--o] original != result, ", original, result);
+ assertSame("[--o] result == operand, ", result, operand);
+ assertSame("[--o] valueOf(n) == operand, ", 6, operand);
+ assertEquals("[--o] n == operand.value, ", 6, operand.intValue());
+ }
+
+ {
+ Integer operand, original;
+ original = operand = 8;
+ operand += 2;
+ assertNotSame("[+=] original != operand, ", original, operand);
+ assertSame("[+=] valueOf(n) == operand, ", 10, operand);
+ assertEquals("[+=] n == operand.value, ", 10, operand.intValue());
+ }
+
+ {
+ Integer operand, original;
+ original = operand = 11;
+ operand -= 2;
+ assertNotSame("[-=] original != operand, ", original, operand);
+ assertSame("[-=] valueOf(n) == operand, ", 9, operand);
+ assertEquals("[-=] n == operand.value, ", 9, operand.intValue());
+ }
+
+ {
+ Integer operand, original;
+ original = operand = 21;
+ operand *= 2;
+ assertNotSame("[*=] original != operand, ", original, operand);
+ assertSame("[*=] valueOf(n) == operand, ", 42, operand);
+ assertEquals("[*=] n == operand.value, ", 42, operand.intValue());
+ }
+
+ {
+ Integer operand, original;
+ original = operand = 30;
+ operand /= 2;
+ assertNotSame("[/=] original != operand, ", original, operand);
+ assertSame("[/=] valueOf(n) == operand, ", 15, operand);
+ assertEquals("[/=] n == operand.value, ", 15, operand.intValue());
+ }
+
+ {
+ Integer operand, original;
+ original = operand = 123;
+ operand %= 100;
+ assertNotSame("[%=] original != operand, ", original, operand);
+ assertSame("[%=] valueOf(n) == operand, ", 23, operand);
+ assertEquals("[%=] n == operand.value, ", 23, operand.intValue());
+ }
+
+ {
+ Integer operand, original;
+ original = operand = 0x55;
+ operand &= 0xF;
+ assertNotSame("[&=] original != operand, ", original, operand);
+ assertSame("[&=] valueOf(n) == operand, ", 0x5, operand);
+ assertEquals("[&=] n == operand.value, ", 0x5, operand.intValue());
+ }
+
+ {
+ Integer operand, original;
+ original = operand = 0x55;
+ operand |= 0xF;
+ assertNotSame("[|=] original != operand, ", original, operand);
+ assertSame("[|=] valueOf(n) == operand, ", 0x5F, operand);
+ assertEquals("[|=] n == operand.value, ", 0x5F, operand.intValue());
+ }
+
+ {
+ Integer operand, original;
+ original = operand = 0x55;
+ operand ^= 0xF;
+ assertNotSame("[&=] original != operand, ", original, operand);
+ assertSame("[&=] valueOf(n) == operand, ", 0x5A, operand);
+ assertEquals("[&=] n == operand.value, ", 0x5A, operand.intValue());
+ }
+
+ {
+ Integer operand, original;
+ original = operand = 0x3F;
+ operand <<= 1;
+ assertNotSame("[<<=] original != operand, ", original, operand);
+ assertSame("[<<=] valueOf(n) == operand, ", 0x7E, operand);
+ assertEquals("[<<=] n == operand.value, ", 0x7E, operand.intValue());
+ }
+
+ {
+ Integer operand, original;
+ original = operand = -16;
+ operand >>= 1;
+ assertNotSame("[>>=] original != operand, ", original, operand);
+ assertSame("[>>=] valueOf(n) == operand, ", -8, operand);
+ assertEquals("[>>=] n == operand.value, ", -8, operand.intValue());
+ }
+
+ {
+ Integer operand, original;
+ original = operand = -1;
+ operand >>>= 1;
+ assertNotSame("[>>>=] original != operand, ", original, operand);
+ assertEquals("[>>>=] valueOf(n).equals(operand), ",
+ Integer.valueOf(0x7FFFFFFF), operand);
+ assertEquals("[>>>=] n == operand.value, ", 0x7FFFFFFF,
+ operand.intValue());
+ }
+ }
+
+ /**
+ * Tests ++ and -- on all boxed types. Use assertNotSame to ensure that a new
+ * wrapper is created instead of modifying the original boxed value in place.
+ * (Issue 2446).
+ */
+ public void testIncrDecr() {
+ {
+ // these initial tests are miscellaneous one-off tests
+ Byte originalBoxedByte = boxedByte;
+
+ assertEquals(unboxedByte, (byte) boxedByte++);
+ assertEquals(unboxedByte + 1, (byte) boxedByte);
+ boxedByte = originalBoxedByte;
+
+ Integer[] ary = new Integer[] {0, 10, 20, 30, 40, 50};
+ Integer idx = 2;
+ assertEquals(20, (int) ary[idx++]++);
+ assertEquals(21, (int) ary[2]);
+ assertEquals(3, (int) idx);
+ assertEquals(40, (int) ary[idx += 1]);
+ assertEquals(4, (int) idx);
+ }
+ // the rest of this method tests all boxed types under ++ and --
+ {
+ Byte originalBoxedByte = boxedByte;
+ boxedByte++;
+ assertNotSame("Boxed byte modified in place", boxedByte,
+ originalBoxedByte);
+ assertEquals(unboxedByte + 1, (byte) boxedByte);
+ boxedByte = originalBoxedByte;
+ ++boxedByte;
+ assertNotSame("Boxed byte modified in place", boxedByte,
+ originalBoxedByte);
+ assertEquals(unboxedByte + 1, (byte) boxedByte);
+ boxedByte = originalBoxedByte;
+ boxedByte--;
+ assertNotSame("Boxed byte modified in place", boxedByte,
+ originalBoxedByte);
+ assertEquals(unboxedByte - 1, (byte) boxedByte);
+ boxedByte = originalBoxedByte;
+ --boxedByte;
+ assertNotSame("Boxed byte modified in place", boxedByte,
+ originalBoxedByte);
+ assertEquals(unboxedByte - 1, (byte) boxedByte);
+ boxedByte = originalBoxedByte;
+ }
+ {
+ Character originalBoxedChar = boxedChar;
+ boxedChar++;
+ assertNotSame("Boxed character modified in place", boxedChar,
+ originalBoxedChar);
+ assertEquals(unboxedChar + 1, (char) boxedChar);
+ boxedChar = originalBoxedChar;
+ ++boxedChar;
+ assertNotSame("Boxed character modified in place", boxedChar,
+ originalBoxedChar);
+ assertEquals(unboxedChar + 1, (char) boxedChar);
+ boxedChar = originalBoxedChar;
+ boxedChar--;
+ assertNotSame("Boxed character modified in place", boxedChar,
+ originalBoxedChar);
+ assertEquals(unboxedChar - 1, (char) boxedChar);
+ boxedChar = originalBoxedChar;
+ --boxedChar;
+ assertNotSame("Boxed character modified in place", boxedChar,
+ originalBoxedChar);
+ assertEquals(unboxedChar - 1, (char) boxedChar);
+ boxedChar = originalBoxedChar;
+ }
+ {
+ Short originalBoxedShort = boxedShort;
+ boxedShort++;
+ assertNotSame("Boxed short modified in place", boxedShort,
+ originalBoxedShort);
+ assertEquals(unboxedShort + 1, (short) boxedShort);
+ boxedShort = originalBoxedShort;
+ ++boxedShort;
+ assertNotSame("Boxed short modified in place", boxedShort,
+ originalBoxedShort);
+ assertEquals(unboxedShort + 1, (short) boxedShort);
+ boxedShort = originalBoxedShort;
+ boxedShort--;
+ assertNotSame("Boxed short modified in place", boxedShort,
+ originalBoxedShort);
+ assertEquals(unboxedShort - 1, (short) boxedShort);
+ boxedShort = originalBoxedShort;
+ --boxedShort;
+ assertNotSame("Boxed short modified in place", boxedShort,
+ originalBoxedShort);
+ assertEquals(unboxedShort - 1, (short) boxedShort);
+ boxedShort = originalBoxedShort;
+ }
+ {
+ Integer originalBoxedInt = boxedInt;
+ boxedInt++;
+ assertNotSame("Boxed int modified in place", boxedInt, originalBoxedInt);
+ assertEquals(unboxedInt + 1, (int) boxedInt);
+ boxedInt = originalBoxedInt;
+ ++boxedInt;
+ assertNotSame("Boxed int modified in place", boxedInt, originalBoxedInt);
+ assertEquals(unboxedInt + 1, (int) boxedInt);
+ boxedInt = originalBoxedInt;
+ boxedInt--;
+ assertNotSame("Boxed int modified in place", boxedInt, originalBoxedInt);
+ assertEquals(unboxedInt - 1, (int) boxedInt);
+ boxedInt = originalBoxedInt;
+ --boxedInt;
+ assertNotSame("Boxed int modified in place", boxedInt, originalBoxedInt);
+ assertEquals(unboxedInt - 1, (int) boxedInt);
+ boxedInt = originalBoxedInt;
+ }
+ {
+ Long originalBoxedLong = boxedLong;
+ boxedLong++;
+ assertNotSame("Boxed long modified in place", boxedLong,
+ originalBoxedLong);
+ assertEquals(unboxedLong + 1, (long) boxedLong);
+ boxedLong = originalBoxedLong;
+ ++boxedLong;
+ assertNotSame("Boxed long modified in place", boxedLong,
+ originalBoxedLong);
+ assertEquals(unboxedLong + 1, (long) boxedLong);
+ boxedLong = originalBoxedLong;
+ boxedLong--;
+ assertNotSame("Boxed long modified in place", boxedLong,
+ originalBoxedLong);
+ assertEquals(unboxedLong - 1, (long) boxedLong);
+ boxedLong = originalBoxedLong;
+ --boxedLong;
+ assertNotSame("Boxed long modified in place", boxedLong,
+ originalBoxedLong);
+ assertEquals(unboxedLong - 1, (long) boxedLong);
+ boxedLong = originalBoxedLong;
+ }
+ {
+ Float originalBoxedFloat = boxedFloat;
+ boxedFloat++;
+ assertNotSame("Boxed float modified in place", boxedFloat,
+ originalBoxedFloat);
+ assertEquals(unboxedFloat + 1, (float) boxedFloat);
+ boxedFloat = originalBoxedFloat;
+ ++boxedFloat;
+ assertNotSame("Boxed float modified in place", boxedFloat,
+ originalBoxedFloat);
+ assertEquals(unboxedFloat + 1, (float) boxedFloat);
+ boxedFloat = originalBoxedFloat;
+ boxedFloat--;
+ assertNotSame("Boxed float modified in place", boxedFloat,
+ originalBoxedFloat);
+ assertEquals(unboxedFloat - 1, (float) boxedFloat);
+ boxedFloat = originalBoxedFloat;
+ --boxedFloat;
+ assertNotSame("Boxed float modified in place", boxedFloat,
+ originalBoxedFloat);
+ assertEquals(unboxedFloat - 1, (float) boxedFloat);
+ boxedFloat = originalBoxedFloat;
+ }
+ {
+ Double originalBoxedDouble = boxedDouble;
+ boxedDouble++;
+ assertNotSame("Boxed double modified in place", boxedDouble,
+ originalBoxedDouble);
+ assertEquals(unboxedDouble + 1, (double) boxedDouble);
+ boxedDouble = originalBoxedDouble;
+ ++boxedDouble;
+ assertNotSame("Boxed double modified in place", boxedDouble,
+ originalBoxedDouble);
+ assertEquals(unboxedDouble + 1, (double) boxedDouble);
+ boxedDouble = originalBoxedDouble;
+ boxedDouble--;
+ assertNotSame("Boxed double modified in place", boxedDouble,
+ originalBoxedDouble);
+ assertEquals(unboxedDouble - 1, (double) boxedDouble);
+ boxedDouble = originalBoxedDouble;
+ --boxedDouble;
+ assertNotSame("Boxed double modified in place", boxedDouble,
+ originalBoxedDouble);
+ assertEquals(unboxedDouble - 1, (double) boxedDouble);
+ boxedDouble = originalBoxedDouble;
+ }
+ }
+
public void testUnboxing() {
boolean boolean_ = boxedBoolean;
assertTrue(boolean_ == boxedBoolean.booleanValue());