You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@groovy.apache.org by "Eric Milles (Jira)" <ji...@apache.org> on 2022/06/14 14:29:00 UTC

[jira] [Comment Edited] (GROOVY-10657) Generated code for overloaded assignment operator prevents garbage collection

    [ https://issues.apache.org/jira/browse/GROOVY-10657?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17554129#comment-17554129 ] 

Eric Milles edited comment on GROOVY-10657 at 6/14/22 2:28 PM:
---------------------------------------------------------------

As we process the assignment expression `employees["1"] = "Bob"` we have to deal with the RHS to understand what it is (see below).  Just after this, we need to stash the top operand into a temp var so that we can push `employees` and `"1"` onto the stack.  Then the RHS is pushed back.  At this point, the temp could be discarded I suppose.

From {{BinaryExpressionHelper}}:
{code:java}
    public void evaluateEqual(final BinaryExpression expression, final boolean defineVariable) {
        AsmClassGenerator acg = controller.getAcg();
        CompileStack compileStack = controller.getCompileStack();
        OperandStack operandStack = controller.getOperandStack();
        Expression leftExpression = expression.getLeftExpression();
        Expression rightExpression = expression.getRightExpression();
        boolean directAssignment = defineVariable && !(leftExpression instanceof TupleExpression);

        if (directAssignment && rightExpression instanceof EmptyExpression) {
            VariableExpression ve = (VariableExpression) leftExpression;
            BytecodeVariable var = compileStack.defineVariable(ve, controller.getTypeChooser().resolveType(ve, controller.getClassNode()), false);
            operandStack.loadOrStoreVariable(var, false);
            return;
        }
        // evaluate the RHS and store the result
        // TODO: LHS has not been visited, it could be a variable in a closure and type chooser is not aware.
        ClassNode lhsType = controller.getTypeChooser().resolveType(leftExpression, controller.getClassNode());
        if (rightExpression instanceof ListExpression && lhsType.isArray()) {
            ListExpression list = (ListExpression) rightExpression;
            ArrayExpression array = new ArrayExpression(lhsType.getComponentType(), list.getExpressions());
            array.setSourcePosition(list);
            array.visit(acg);
        } else if (rightExpression instanceof EmptyExpression) {
            loadInitValue(leftExpression.getType()); // TODO: lhsType?
        } else {
            rightExpression.visit(acg);
        }

        ClassNode rhsType = operandStack.getTopOperand();
        int rhsValueId = compileStack.defineTemporaryVariable("$rhs", rhsType, true);
{code}


was (Author: emilles):
As we process the assignment expression `employees["1"] = "Bob"` we have to deal with the RHS to understand what it is (see below).  Just after this, we need to stash the top operand into a temp var so that we can push `employees` and `"1"` onto the stack.  Then the RHS is pushed back.  At this point, the temp could be discarded I suppose.

From {{BinaryExpressionHelper}}:
{code:java}
    public void evaluateEqual(final BinaryExpression expression, final boolean defineVariable) {
        AsmClassGenerator acg = controller.getAcg();
        CompileStack compileStack = controller.getCompileStack();
        OperandStack operandStack = controller.getOperandStack();
        Expression leftExpression = expression.getLeftExpression();
        Expression rightExpression = expression.getRightExpression();
        boolean directAssignment = defineVariable && !(leftExpression instanceof TupleExpression);

        if (directAssignment && rightExpression instanceof EmptyExpression) {
            VariableExpression ve = (VariableExpression) leftExpression;
            BytecodeVariable var = compileStack.defineVariable(ve, controller.getTypeChooser().resolveType(ve, controller.getClassNode()), false);
            operandStack.loadOrStoreVariable(var, false);
            return;
        }
        // evaluate the RHS and store the result
        // TODO: LHS has not been visited, it could be a variable in a closure and type chooser is not aware.
        ClassNode lhsType = controller.getTypeChooser().resolveType(leftExpression, controller.getClassNode());
        if (rightExpression instanceof ListExpression && lhsType.isArray()) {
            ListExpression list = (ListExpression) rightExpression;
            ArrayExpression array = new ArrayExpression(lhsType.getComponentType(), list.getExpressions());
            array.setSourcePosition(list);
            array.visit(acg);
        } else if (rightExpression instanceof EmptyExpression) {
            loadInitValue(leftExpression.getType()); // TODO: lhsType?
        } else {
            rightExpression.visit(acg);
        }

        ClassNode rhsType = operandStack.getTopOperand();
        int rhsValueId;
{code}

> Generated code for overloaded assignment operator prevents garbage collection
> -----------------------------------------------------------------------------
>
>                 Key: GROOVY-10657
>                 URL: https://issues.apache.org/jira/browse/GROOVY-10657
>             Project: Groovy
>          Issue Type: Bug
>          Components: Compiler
>    Affects Versions: 4.0.3
>            Reporter: Jonathan Baxter
>            Priority: Major
>
> The groovy code 
> {code:java}
> obj["lhs"] = "rhs"{code}
> ostensibly converts to 
> {code:java}
> obj.putAt("lhs","rhs")
> {code}
> if {{obj}} has the appropriate {{putAt}} method.
> However , in the process of tracking down a memory leak, I discovered that assignment is not exactly equivalent to invocation of the {{putAt}} method. Specifically, the code generated by the compiler is equivalent to creating an additional temporary variable to hold the rhs of the assignment, and then invoking the {{putAt}} method with that temporary variable as its second argument:
> {code:java}
> String string = "rhs"
> obj.putAt("lhs",string){code}
> The problem with this is that since {{"rhs"}} now has a live reference ({{{}string{}}}), it is not eligible for garbage collection. 
> In my particular use case, {{"rhs"}} is a very large object that I would like to be automatically garbage collected after the assignment is executed, but before the script has finished. But that means I have to use the underlying {{putAt}} method rather than the assignment operator, which rather negates the benefits of overloading the assignment operator.
> One potential fix would be to simply null out any temporary variables constructed by the compiler, so that 
> {code:java}
> obj["lhs"] = "rhs"
> {code}
>  becomes
> {code:java}
> String string = "rhs" 
> obj.putAt("lhs",string) 
> string = null <----- "rhs" is now eligible for garbage-collection{code}



--
This message was sent by Atlassian Jira
(v8.20.7#820007)