Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3e61df6b authored by Makoto Onuki's avatar Makoto Onuki Committed by Android (Google) Code Review
Browse files

Merge "Teach Ravenizer how to handle class remove / rename" into main

parents c2c2fb8a b9f552f0
Loading
Loading
Loading
Loading
+55 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * 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.android.ravenwoodtest.coretest;

import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.fail;

import android.ravenwood.annotation.RavenwoodRemove;

import org.junit.Test;

import java.util.Arrays;

public class RavenwoodRavenizerTest {
    @Test
    public void testRemoveClass() {
        try {
            var c = ToBeRemoved.class;
            fail("Class ToBeRemoved expected to be removed");
        } catch (java.lang.NoClassDefFoundError e) {
            // these are okay
        }
    }

    @Test
    public void testRemoveMethod() {
        var c = ToBeKept.class;
        assertThat(Arrays.stream(c.getDeclaredMethods())
                .anyMatch(m -> m.getName().equals("toBeRemoved"))).isFalse();
    }
}

@RavenwoodRemove
class ToBeRemoved {
}

class ToBeKept {
    @RavenwoodRemove
    public static void toBeRemoved() {
    }
}
+27 −0
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import com.android.hoststubgen.filters.AnnotationBasedFilter
import com.android.hoststubgen.filters.ClassWidePolicyPropagatingFilter
import com.android.hoststubgen.filters.ConstantFilter
import com.android.hoststubgen.filters.DefaultHookInjectingFilter
import com.android.hoststubgen.filters.FilterPolicy
import com.android.hoststubgen.filters.FilterPolicyWithReason
import com.android.hoststubgen.filters.FilterRemapper
import com.android.hoststubgen.filters.ImplicitOutputFilter
import com.android.hoststubgen.filters.KeepNativeFilter
@@ -29,6 +31,7 @@ import com.android.hoststubgen.filters.SanitizationFilter
import com.android.hoststubgen.filters.TextFileFilterPolicyBuilder
import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
import com.android.hoststubgen.utils.ClassPredicate
import com.android.hoststubgen.utils.ZipEntryData
import com.android.hoststubgen.visitors.ImplGeneratingAdapter
import com.android.hoststubgen.visitors.JdkPatchVisitor
import com.android.hoststubgen.visitors.PackageRedirectRemapper
@@ -132,6 +135,30 @@ class HostStubGenClassProcessor(
        return cw.toByteArray()
    }

    data class ClassZipEntryInfo(
        val classInternalName: String,
        val renamedEntryName: String,
        val policy: FilterPolicyWithReason,
    )

    fun applyFilterOnClass(zipEntry: ZipEntryData): ClassZipEntryInfo? {
        val classInternalName = zipEntry.name.removeSuffix(".class")
        val classPolicy = filter.getPolicyForClass(classInternalName)
        if (classPolicy.policy == FilterPolicy.Remove) {
            log.d("Removing class: %s %s", classInternalName, classPolicy)
            return null
        }
        // If we're applying a remapper, we need to rename the file too.
        var newName = zipEntry.name
        remapper.mapType(classInternalName)?.let { remappedName ->
            if (remappedName != classInternalName) {
                log.d("Renaming class file: %s -> %s", classInternalName, remappedName)
                newName = "$remappedName.class"
            }
        }
        return ClassZipEntryInfo(classInternalName, newName, classPolicy)
    }

    companion object {
        /**
         * Build the filter, which decides what classes/methods/fields should be put in stub or impl
+3 −17
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@
package com.android.hoststubgen

import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.filters.FilterPolicy
import com.android.hoststubgen.filters.printAsTextPolicy
import com.android.hoststubgen.utils.ConcurrentZipFile
import com.android.hoststubgen.utils.ZipEntryData
@@ -139,25 +138,12 @@ class HostStubGen(val options: HostStubGenOptions) {
        entry: ZipEntryData,
        processor: HostStubGenClassProcessor
    ): ZipEntryData? {
        val classInternalName = entry.name.replaceFirst("\\.class$".toRegex(), "")
        val classPolicy = processor.filter.getPolicyForClass(classInternalName)
        if (classPolicy.policy == FilterPolicy.Remove) {
            log.d("Removing class: %s %s", classInternalName, classPolicy)
            return null
        }
        // If we're applying a remapper, we need to rename the file too.
        var newName = entry.name
        processor.remapper.mapType(classInternalName)?.let { remappedName ->
            if (remappedName != classInternalName) {
                log.d("Renaming class file: %s -> %s", classInternalName, remappedName)
                newName = "$remappedName.class"
            }
        }
        val entryInfo = processor.applyFilterOnClass(entry) ?: return null

        log.v("Creating class: %s Policy: %s", classInternalName, classPolicy)
        log.v("Creating class: %s Policy: %s", entryInfo.classInternalName, entryInfo.policy)
        log.withIndent {
            val data = processor.processClassBytecode(entry.data)
            return ZipEntryData.fromBytes(newName, data)
            return ZipEntryData.fromBytes(entryInfo.renamedEntryName, data)
        }
    }
}
+8 −1
Original line number Diff line number Diff line
@@ -122,13 +122,20 @@ class Ravenizer {

                    if (className?.shouldBypass() == false) {
                        stats.processedClasses.incrementAndGet()

                        val entryInfo = processor.applyFilterOnClass(entry)
                            ?: return@process null

                        log.v("Creating class: %s Policy: %s",
                            entryInfo.classInternalName, entryInfo.policy)

                        var classBytes = entry.data
                        if (RunnerRewritingAdapter.shouldProcess(processor.allClasses, className)) {
                            classBytes = ravenizeSingleClass(classBytes, processor.allClasses)
                        }
                        classBytes = processor.processClassBytecode(classBytes)
                        // Create a new entry
                        ZipEntryData.fromBytes(entry.name, classBytes)
                        ZipEntryData.fromBytes(entryInfo.renamedEntryName, classBytes)
                    } else {
                        // Do not process and return the original entry
                        entry