opt/remove_empty_classes/RemoveEmptyClasses.cpp (131 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <stdio.h>
#include <unordered_set>
#include "DexClass.h"
#include "DexUtil.h"
#include "PassManager.h"
#include "ReachableClasses.h"
#include "RemoveEmptyClasses.h"
#include "Trace.h"
#include "Walkers.h"
constexpr const char* METRIC_REMOVED_EMPTY_CLASSES =
"num_empty_classes_removed";
void remove_clinit_if_trivial(DexClass* cls) {
DexMethod* clinit = cls->get_clinit();
if (clinit && method::is_trivial_clinit(*clinit->get_code())) {
cls->remove_method(clinit);
}
}
bool is_empty_class(DexClass* cls,
ConcurrentSet<const DexType*>& class_references) {
bool empty_class = cls->get_dmethods().empty() &&
cls->get_vmethods().empty() &&
cls->get_sfields().empty() && cls->get_ifields().empty();
uint32_t access = cls->get_access();
auto name = cls->get_type()->get_name()->c_str();
TRACE(EMPTY, 4, ">> Empty Analysis for %s", name);
TRACE(EMPTY, 4, " no methods or fields: %d", empty_class);
TRACE(EMPTY, 4, " can delete: %d", can_delete(cls));
TRACE(EMPTY, 4, " not interface: %d",
!(access & DexAccessFlags::ACC_INTERFACE));
TRACE(EMPTY, 4, " references: %zu",
class_references.count(cls->get_type()));
bool remove = empty_class && can_delete(cls) &&
!(access & DexAccessFlags::ACC_INTERFACE) &&
class_references.count(cls->get_type()) == 0;
TRACE(EMPTY, 4, " remove: %d", remove);
return remove;
}
void process_annotation(ConcurrentSet<const DexType*>* class_references,
DexAnnotation* annotation) {
std::vector<DexType*> ltype;
annotation->gather_types(ltype);
for (DexType* dextype : ltype) {
TRACE(EMPTY, 4, "Adding type annotation to keep list: %s",
dextype->get_name()->c_str());
class_references->insert(dextype);
}
}
void process_proto(ConcurrentSet<const DexType*>* class_references,
DexMethodRef* meth) {
// Types referenced in protos.
auto const& proto = meth->get_proto();
class_references->insert(type::get_element_type_if_array(proto->get_rtype()));
for (auto const& ptype : *proto->get_args()) {
class_references->insert(type::get_element_type_if_array(ptype));
}
}
void process_code(ConcurrentSet<const DexType*>* class_references,
DexMethod* meth,
IRCode& code) {
// Types referenced in code.
for (auto const& mie : InstructionIterable(meth->get_code())) {
auto opcode = mie.insn;
if (opcode->has_type()) {
auto typ = type::get_element_type_if_array(opcode->get_type());
TRACE(EMPTY, 4, "Adding type from code to keep list: %s",
typ->get_name()->c_str());
class_references->insert(typ);
} else if (opcode->has_field()) {
auto const& field = opcode->get_field();
class_references->insert(
type::get_element_type_if_array(field->get_class()));
class_references->insert(
type::get_element_type_if_array(field->get_type()));
} else if (opcode->has_method()) {
auto const& m = opcode->get_method();
process_proto(class_references, m);
}
}
// Also gather exception types that are caught.
std::vector<DexType*> catch_types;
code.gather_catch_types(catch_types);
for (auto& caught_type : catch_types) {
class_references->insert(caught_type);
}
}
size_t remove_empty_classes(Scope& classes) {
// class_references is a set of type names which represent classes
// which should not be deleted even if they are deemed to be empty.
ConcurrentSet<const DexType*> class_references;
walk::parallel::classes(classes, [&class_references](DexClass* cls) {
std::vector<DexClass*> singleton_cls{cls};
walk::annotations(singleton_cls, [&](DexAnnotation* annotation) {
process_annotation(&class_references, annotation);
});
// Check the method protos and all the code.
walk::methods(singleton_cls, [&class_references](DexMethod* meth) {
process_proto(&class_references, meth);
auto code = meth->get_code();
if (!code) {
return;
}
process_code(&class_references, meth, *code);
});
// Ennumerate super classes and remove trivial clinit if the class has any.
remove_clinit_if_trivial(cls);
DexType* s = cls->get_super_class();
class_references.insert(s);
// Ennumerate fields.
walk::fields(singleton_cls, [&class_references](DexField* field) {
class_references.insert(
type::get_element_type_if_array(field->get_type()));
});
});
size_t classes_before_size = classes.size();
TRACE(EMPTY, 3, "About to erase classes.");
classes.erase(remove_if(classes.begin(), classes.end(),
[&](DexClass* cls) {
return is_empty_class(cls, class_references);
}),
classes.end());
auto num_classes_removed = classes_before_size - classes.size();
TRACE(EMPTY, 1, "Empty classes removed: %ld", num_classes_removed);
return num_classes_removed;
}
void RemoveEmptyClassesPass::run_pass(DexStoresVector& stores,
ConfigFiles& /* conf */,
PassManager& mgr) {
if (mgr.no_proguard_rules()) {
TRACE(EMPTY, 1,
"RemoveEmptyClassesPass not run because no ProGuard configuration "
"was provided.");
return;
}
auto scope = build_class_scope(stores);
auto num_empty_classes_removed = remove_empty_classes(scope);
mgr.incr_metric(METRIC_REMOVED_EMPTY_CLASSES, num_empty_classes_removed);
post_dexen_changes(scope, stores);
}
static RemoveEmptyClassesPass s_pass;