Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Child class overrides lambda in parent class #109

Closed
calvarez-ov opened this issue Sep 13, 2016 · 6 comments
Closed

Child class overrides lambda in parent class #109

calvarez-ov opened this issue Sep 13, 2016 · 6 comments

Comments

@calvarez-ov
Copy link

calvarez-ov commented Sep 13, 2016

This was initially reported in the gradle-retrolambda project, but the problem appears to come from this project.

Copying the report here:


public abstract class ParentActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getContentView());
        findViewById(R.id.button_parent).setOnClickListener(view -> Toast.makeText(this, "Parent onClick", Toast.LENGTH_LONG).show());
    }

    public abstract int getContentView();
}
public class MainActivity extends ParentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        findViewById(R.id.button_child).setOnClickListener(view -> Toast.makeText(this, "Child onClick", Toast.LENGTH_LONG).show());
    }

    @Override
    public int getContentView() {
        return R.layout.activity_main;
    }
}

Clicking on button_parent shows a Toast with "Child onClick"

test app: https://github.com/cpalasanu/RetrolambdaTest


Using the cfr decompiler, we see that the child activity class has a method that overrides the parent activity class:

Parent:

/*
 * Decompiled with CFR 0_118.
 * 
 * Could not load the following classes:
 *  android.content.Context
 *  android.os.Bundle
 *  android.support.annotation.Nullable
 *  android.support.v7.app.AppCompatActivity
 *  android.view.View
 *  android.view.View$OnClickListener
 *  android.widget.Toast
 */
package com.orange.retrolambdatest;

import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;
import com.orange.retrolambdatest.ParentActivity$$Lambda$1;

public abstract class ParentActivity
extends AppCompatActivity {
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(this.getContentView());
        this.findViewById(2131427412).setOnClickListener(ParentActivity$$Lambda$1.lambdaFactory$(this));
    }

    public abstract int getContentView();

    /* synthetic */ void lambda$onCreate$0(View view) {
        Toast.makeText((Context)this, (CharSequence)"Parent onClick", (int)1).show();
    }
}

Child:

/*
 * Decompiled with CFR 0_118.
 * 
 * Could not load the following classes:
 *  android.content.Context
 *  android.os.Bundle
 *  android.view.View
 *  android.view.View$OnClickListener
 *  android.widget.Toast
 */
package com.orange.retrolambdatest;

import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import com.orange.retrolambdatest.MainActivity$$Lambda$1;
import com.orange.retrolambdatest.ParentActivity;

public class MainActivity
extends ParentActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.findViewById(2131427413).setOnClickListener(MainActivity$$Lambda$1.lambdaFactory$(this));
    }

    @Override
    public int getContentView() {
        return 2130968602;
    }

    @Override
    /* synthetic */ void lambda$onCreate$0(View view) {
        Toast.makeText((Context)this, (CharSequence)"Child onClick", (int)1).show();
    }
}

Both parent and child have the same method lambda$onCreate$0(View view)


I did some tests building the retrolambda project locally. It appears the bug appears in this commit: bf93245 (between releases 2.2.0 and 2.3.0) The PR for that commit: #86

I have not done any investigation to understand what part of that commit introduces the bug.

@calvarez-ov
Copy link
Author

calvarez-ov commented Sep 13, 2016

For comparison:
Before the given commit:
Parent:

    private /* synthetic */ void lambda$onCreate$0(View view) {
        Toast.makeText((Context)this, (CharSequence)"Parent onClick", (int)1).show();
    }

    static /* synthetic */ void access$lambda$0(ParentActivity parentActivity, View view) {
        parentActivity.lambda$onCreate$0(view);
    }

Child:

    private /* synthetic */ void lambda$onCreate$0(View view) {
        Toast.makeText((Context)this, (CharSequence)"Child onClick", (int)1).show();
    }

    static /* synthetic */ void access$lambda$0(MainActivity mainActivity, View view) {
        mainActivity.lambda$onCreate$0(view);
    }

After:
Parent:

    /* synthetic */ void lambda$onCreate$0(View view) {
        Toast.makeText((Context)this, (CharSequence)"Parent onClick", (int)1).show();
    }

Child:

    @Override
    /* synthetic */ void lambda$onCreate$0(View view) {
        Toast.makeText((Context)this, (CharSequence)"Child onClick", (int)1).show();
    }

@calvarez-ov
Copy link
Author

Related issue: #81

@calvarez-ov
Copy link
Author

calvarez-ov commented Sep 16, 2016

Possibly related to (or even a duplicate of) #95

caarmen added a commit to caarmen/retrolambda that referenced this issue Sep 17, 2016
caarmen added a commit to caarmen/retrolambda that referenced this issue Sep 17, 2016
If a child and parent class both have a lambda method of the same name,
make the child lambda private, with an accessor, so that it doesn't override the parent
lambda.
caarmen added a commit to caarmen/retrolambda that referenced this issue Sep 17, 2016
caarmen added a commit to caarmen/retrolambda that referenced this issue Sep 19, 2016
Issue luontola#109: Add unit test to make sure child lambdas don't hide paren…
caarmen added a commit to caarmen/retrolambda that referenced this issue Sep 19, 2016
Issue luontola#109: Prevent child lambdas overriding parent lambdas.
@luontola
Copy link
Owner

luontola commented Oct 17, 2016

Thanks for your work on fixing this, especially @caarmen, and sorry about taking so long to respond.

The origin of the bug is the access &= ~ACC_PRIVATE; line in BackportLambdaInvocations.java which makes private instance methods package-private, thus making them potentially overridable in child classes. I'd like to fix the bug once and for all by removing that line.

One solution is to convert the private instance method into a package-private static method, which won't be overridable by child classes. This can be done safely for lambda implementation methods which only the lambda class will call - for other methods it would fall back to generating a (static) bridge access method.

luontola added a commit that referenced this issue Jan 10, 2017
Issue #109: Add unit test to make sure child lambdas don't hide parent lambdas
luontola added a commit that referenced this issue Jan 10, 2017
@luontola
Copy link
Owner

I've made a fix for this which converts the private instance methods into package-private static methods. Please try it out. I'll release a new version soon.

@luontola
Copy link
Owner

The fix to this has been released in Retrolambda 2.5.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants