Index: src/main/org/codehaus/groovy/classgen/AsmClassGenerator.java =================================================================== --- src/main/org/codehaus/groovy/classgen/AsmClassGenerator.java (revision 20330) +++ src/main/org/codehaus/groovy/classgen/AsmClassGenerator.java (working copy) @@ -4084,9 +4084,14 @@ } private void execMethodAndStoreForSubscriptOperator(String method, Expression expression) { + execMethodAndStoreForSubscriptOperator(method, expression, null); + } + + private void execMethodAndStoreForSubscriptOperator(String method, Expression expression, + Expression getAtResultExp) { // execute method makeCallSite( - expression, + (getAtResultExp == null ? expression : getAtResultExp), method, MethodCallExpression.NO_ARGUMENTS, false, false, false, false); @@ -4132,20 +4137,54 @@ } protected void evaluatePostfixMethod(String method, Expression expression) { + // GROOVY-4246: arr[rand()]++ should evaulate rand() only once and reuse its result + boolean getAtOp = false; + BinaryExpression be = null; + Expression getAtResultExp = null; + String varName = "tmp_postfix_" + method; + final int idx = compileStack.defineTemporaryVariable(varName, false); + + if(expression instanceof BinaryExpression) { + be = (BinaryExpression) expression; + if (be.getOperation().getType() == Types.LEFT_SQUARE_BRACKET) { + getAtOp = true; + be.getRightExpression().visit(this); // execute subscript exp + mv.visitVarInsn(ASTORE, idx); // store it in a local variable + BytecodeExpression newRightExp = new BytecodeExpression() { + public void visit(MethodVisitor mv) { + mv.visitVarInsn(ALOAD, idx); + } + }; + // change the subscript exp to pick up the local var so that the orig exp is not re-executed + be.setRightExpression(newRightExp); + } + + } // load expression.visit(this); // save value for later - int tempIdx = compileStack.defineTemporaryVariable("postfix_" + method, true); + final int tempIdx = compileStack.defineTemporaryVariable("postfix_" + method, true); + if(getAtOp) { + // exp to allow reuse of binary exp already evaluated so that following putAt does not + // executed the getAt() again + getAtResultExp = new BytecodeExpression() { + public void visit(MethodVisitor mv) { + mv.visitVarInsn(ALOAD, tempIdx); + } + }; + } + // execute Method - execMethodAndStoreForSubscriptOperator(method, expression); + execMethodAndStoreForSubscriptOperator(method, expression, getAtResultExp); // remove the result of the method call mv.visitInsn(POP); //reload saved value mv.visitVarInsn(ALOAD, tempIdx); compileStack.removeVar(tempIdx); + compileStack.removeVar(idx); } protected void evaluateInstanceof(BinaryExpression expression) { Index: src/test/groovy/bugs/Groovy4246Bug.groovy =================================================================== --- src/test/groovy/bugs/Groovy4246Bug.groovy (revision 0) +++ src/test/groovy/bugs/Groovy4246Bug.groovy (revision 0) @@ -0,0 +1,44 @@ +/* + * Copyright 2003-2010 the original author or authors. + * + * 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 groovy.bugs + +class Groovy4246Bug extends GroovyTestCase { + void testPostFixExpEvaluations() { + assertScript """ + class Bug4246 { + int randCallCount = 0 + static void main(args) { + new Bug4246() + } + + Bug4246() { + def num = 10 + def arr = [0, 0, 0] + for (def i = 0; i < num; i++) { + arr[rand()]++ + } + assert (arr[0] + arr[1] + arr[2] == num) + assert randCallCount == 10 + } + + int rand() { + randCallCount++ + return new Random().nextInt(3) + } + } + """ + } +}