proguard.optimize.gson.GsonOptimizer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of proguard-base Show documentation
Show all versions of proguard-base Show documentation
ProGuard is a free shrinker, optimizer, obfuscator, and preverifier for Java bytecode
/*
* ProGuard -- shrinking, optimization, obfuscation, and preverification
* of Java bytecode.
*
* Copyright (c) 2002-2020 Guardsquare NV
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package proguard.optimize.gson;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import proguard.Configuration;
import proguard.classfile.*;
import proguard.classfile.attribute.visitor.AllAttributeVisitor;
import proguard.classfile.editor.ClassEditor;
import proguard.classfile.editor.CodeAttributeEditor;
import proguard.classfile.editor.ConstantPoolEditor;
import proguard.classfile.editor.PeepholeEditor;
import proguard.classfile.util.*;
import proguard.classfile.visitor.*;
import proguard.io.ClassPathDataEntry;
import proguard.io.ClassReader;
import proguard.io.ExtraDataEntryNameMap;
import proguard.util.ProcessingFlagSetter;
import proguard.util.ProcessingFlags;
import proguard.util.StringUtil;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import static proguard.classfile.ClassConstants.CLASS_FILE_EXTENSION;
import static proguard.optimize.gson.GsonClassConstants.NAME_EXCLUDER;
import static proguard.optimize.gson.GsonClassConstants.NAME_GSON;
import static proguard.optimize.gson.OptimizedClassConstants.*;
/**
* This is the entry point for the GSON optimizations.
*
* The optimization roughly performs the following steps:
*
* - Find all usages of GSON in the program code: calls to toJson() or fromJson().
*
* - Derive the domain classes that are involved in the GSON call, either
* directly (passed as argument to GSON) or indirectly (a field or element
* type of another domain class).
*
* - Inject optimized methods into the domain classes that serialize and
* deserialize the fields of the domain class without relying on reflection.
*
* - Inject and register GSON type adapters that utilize the optimized
* serialization and deserialization methods on the domain classes and bypass
* the reflective GSON implementation.
*
* As an additional protection measure, the JSON field names are assigned to
* a field index. The mapping between field indices and field names is done
* from the classes _OptimizedJsonReaderImpl and _OptimizedJsonWriterImpl, which
* have String encryption applied to them. This allows injecting serialization
* and deserialization code into the domain classes that have no JSON field
* names stored in them as plain text.
*
* @author Lars Vandenbergh
* @author Rob Coekaerts
*/
public class GsonOptimizer
{
private static final Logger logger = LogManager.getLogger(GsonOptimizer.class);
//*
public static final boolean DEBUG = false;
/*/
public static boolean DEBUG = System.getProperty("go") != null;
//*/
// The order of this matters to ensure that the class references are
// initialized properly.
private static final String[] TEMPLATE_CLASSES =
{
NAME_OPTIMIZED_TYPE_ADAPTER,
NAME_GSON_UTIL,
NAME_OPTIMIZED_JSON_READER,
NAME_OPTIMIZED_JSON_READER_IMPL,
NAME_OPTIMIZED_JSON_WRITER,
NAME_OPTIMIZED_JSON_WRITER_IMPL,
NAME_OPTIMIZED_TYPE_ADAPTER_FACTORY
};
/**
* Performs the Gson optimizations.
*
* @param programClassPool the program class pool on which to perform
* the Gson optimizations.
* @param libraryClassPool the library class pool used to look up
* library class references.
* @param extraDataEntryNameMap the map to which injected class names are
* added.
* @param configuration the configuration that is applied.
* @throws IOException when the injected template classes can not
* be read.
*/
public void execute(ClassPool programClassPool,
ClassPool libraryClassPool,
ExtraDataEntryNameMap extraDataEntryNameMap,
Configuration configuration) throws IOException
{
// Set all fields of Gson to public.
programClassPool.classesAccept(
new ClassNameFilter(StringUtil.join(",",
NAME_GSON,
NAME_EXCLUDER),
new AllFieldVisitor(
new MemberAccessSetter(AccessConstants.PUBLIC))));
// To allow mocking Gson instances in unit tests, we remove the
// final qualifier from the Gson class.
programClassPool.classesAccept(
new ClassNameFilter(NAME_GSON,
new MemberAccessFlagCleaner(AccessConstants.FINAL)));
// Setup Gson context that represents how Gson is used in program
// class pool.
WarningPrinter warningPrinter =
new WarningLogger(logger, configuration.warn);
GsonContext gsonContext = new GsonContext();
gsonContext.setupFor(programClassPool, libraryClassPool, warningPrinter);
// Is there something to optimize at all?
if (gsonContext.gsonDomainClassPool.size() > 0)
{
// Collect fields that need to be serialized and deserialized.
OptimizedJsonInfo serializationInfo = new OptimizedJsonInfo();
OptimizedJsonInfo deserializationInfo = new OptimizedJsonInfo();
OptimizedJsonFieldCollector serializedFieldCollector =
new OptimizedJsonFieldCollector(serializationInfo,
OptimizedJsonFieldCollector.Mode.serialize);
OptimizedJsonFieldCollector deserializedFieldCollector =
new OptimizedJsonFieldCollector(deserializationInfo,
OptimizedJsonFieldCollector.Mode.deserialize);
gsonContext.gsonDomainClassPool
.classesAccept(
new MultiClassVisitor(
new OptimizedJsonFieldVisitor(serializedFieldCollector,
serializedFieldCollector),
new OptimizedJsonFieldVisitor(deserializedFieldCollector,
deserializedFieldCollector)));
// Delete all @SerializedName and @Expose annotations
gsonContext.gsonDomainClassPool
.classesAccept(new GsonAnnotationCleaner(gsonContext.gsonRuntimeSettings));
// Assign random indices to classes and fields.
serializationInfo.assignIndices();
deserializationInfo.assignIndices();
// Inject all serialization and deserialization template classes.
ClassReader helperClassReader =
new ClassReader(false, false, false, false, null,
new MultiClassVisitor(
new ProcessingFlagSetter(ProcessingFlags.INJECTED),
new ClassPresenceFilter(programClassPool, null,
new ClassPoolFiller(programClassPool)),
new ClassReferenceInitializer(programClassPool, libraryClassPool),
new ClassSubHierarchyInitializer()));
for (String clazz : TEMPLATE_CLASSES)
{
helperClassReader.read(new ClassPathDataEntry(clazz + CLASS_FILE_EXTENSION));
for (Clazz domainClass : gsonContext.gsonDomainClassPool.classes())
{
extraDataEntryNameMap.addExtraClassToClass(domainClass.getName(), clazz);
}
}
// Inject serialization and deserialization data structures in
// _OptimizedJsonReaderImpl and _OptimizedJsonWriterImpl.
BranchTargetFinder branchTargetFinder = new BranchTargetFinder();
CodeAttributeEditor codeAttributeEditor =
new CodeAttributeEditor(true, false);
programClassPool
.classesAccept(NAME_OPTIMIZED_JSON_WRITER_IMPL,
new MultiClassVisitor(
// Class encryption is disabled to avoid performance loss.
// new ProcessingFlagSetter(ProcessingFlags.ENCRYPT),
new AllMemberVisitor(
new MemberNameFilter(OptimizedClassConstants.METHOD_NAME_INIT_NAMES,
new MemberDescriptorFilter(OptimizedClassConstants.METHOD_TYPE_INIT_NAMES,
new AllAttributeVisitor(
new OptimizedJsonWriterImplInitializer(programClassPool,
libraryClassPool,
codeAttributeEditor,
serializationInfo)))))));
programClassPool
.classesAccept(NAME_OPTIMIZED_JSON_READER_IMPL,
new MultiClassVisitor(
new AllMemberVisitor(
new MemberNameFilter(OptimizedClassConstants.METHOD_NAME_INIT_NAMES_MAP,
new MemberDescriptorFilter(OptimizedClassConstants.METHOD_TYPE_INIT_NAMES_MAP,
new AllAttributeVisitor(
new OptimizedJsonReaderImplInitializer(programClassPool,
libraryClassPool,
codeAttributeEditor,
deserializationInfo)))))));
// Inject serialization and deserialization code in domain classes.
gsonContext.gsonDomainClassPool
.classesAccept(new ClassAccessFilter(0, AccessConstants.ENUM,
new GsonSerializationOptimizer(programClassPool,
libraryClassPool,
gsonContext.gsonRuntimeSettings,
serializationInfo,
extraDataEntryNameMap)));
gsonContext.gsonDomainClassPool
.classesAccept(new ClassAccessFilter(0, AccessConstants.ENUM,
new GsonDeserializationOptimizer(programClassPool,
libraryClassPool,
gsonContext.gsonRuntimeSettings,
deserializationInfo,
extraDataEntryNameMap)));
gsonContext.gsonDomainClassPool
.classesAccept(new ClassReferenceInitializer(programClassPool, libraryClassPool));
// Inject type adapters for all serialized and deserialized classes that are not abstract and hence can
// be instantiated directly.
Map typeAdapterRegistry = new HashMap();
OptimizedTypeAdapterAdder optimizedTypeAdapterAdder =
new OptimizedTypeAdapterAdder(programClassPool,
libraryClassPool,
codeAttributeEditor,
serializationInfo,
deserializationInfo,
extraDataEntryNameMap,
typeAdapterRegistry,
gsonContext.gsonRuntimeSettings);
gsonContext.gsonDomainClassPool.classesAccept(
new ClassAccessFilter(0, AccessConstants.ABSTRACT,
optimizedTypeAdapterAdder));
// Implement type adapter factory.
programClassPool.classAccept(NAME_OPTIMIZED_TYPE_ADAPTER_FACTORY,
new MultiClassVisitor(
new AllMemberVisitor(
new AllAttributeVisitor(
new PeepholeEditor(branchTargetFinder, codeAttributeEditor,
new OptimizedTypeAdapterFactoryInitializer(programClassPool,
codeAttributeEditor,
typeAdapterRegistry,
gsonContext.gsonRuntimeSettings)))),
new ClassReferenceInitializer(programClassPool, libraryClassPool)));
// Add excluder field to Gson class if not present to support
// @Expose in earlier Gson versions (down to 2.1).
ProgramClass gsonClass = (ProgramClass) programClassPool.getClass(NAME_GSON);
MemberCounter memberCounter = new MemberCounter();
gsonClass.accept(new NamedFieldVisitor(FIELD_NAME_EXCLUDER,
FIELD_TYPE_EXCLUDER,
memberCounter));
boolean addExcluder = memberCounter.getCount() == 0;
if (addExcluder)
{
ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor(gsonClass,
programClassPool,
libraryClassPool);
int nameIndex = constantPoolEditor.addUtf8Constant(FIELD_NAME_EXCLUDER);
int descriptorIndex = constantPoolEditor.addUtf8Constant(FIELD_TYPE_EXCLUDER);
ProgramField field = new ProgramField(AccessConstants.PUBLIC,
nameIndex,
descriptorIndex,
null);
ClassEditor classEditor = new ClassEditor(gsonClass);
classEditor.addField(field);
gsonClass.fieldsAccept(new ClassReferenceInitializer(programClassPool, libraryClassPool));
gsonClass.constantPoolEntriesAccept(new ClassReferenceInitializer(programClassPool, libraryClassPool));
}
// Inject code that registers inject type adapter factory for optimized domain classes in Gson constructor.
programClassPool.classAccept(NAME_GSON,
new MultiClassVisitor(
new AllMemberVisitor(
new MemberNameFilter(ClassConstants.METHOD_NAME_INIT,
new GsonConstructorPatcher(codeAttributeEditor, addExcluder))),
new ClassReferenceInitializer(programClassPool, libraryClassPool)));
logger.info(" Number of optimized serializable classes: {}", gsonContext.gsonDomainClassPool.size() );
if (logger.getLevel().isLessSpecificThan(Level.DEBUG))
{
// Inject instrumentation code in Gson.toJson() and Gson.fromJson().
programClassPool.classAccept(NAME_GSON,
new AllMethodVisitor(
new MultiMemberVisitor(
new MemberNameFilter(GsonClassConstants.METHOD_NAME_TO_JSON,
new MemberDescriptorFilter(StringUtil.join(",",
GsonClassConstants.METHOD_TYPE_TO_JSON_OBJECT_TYPE_WRITER,
GsonClassConstants.METHOD_TYPE_TO_JSON_JSON_ELEMENT_WRITER),
new AllAttributeVisitor(
new PeepholeEditor(branchTargetFinder, codeAttributeEditor,
new GsonInstrumentationAdder(programClassPool,
libraryClassPool,
codeAttributeEditor))))),
new MemberNameFilter(GsonClassConstants.METHOD_NAME_FROM_JSON,
new MemberDescriptorFilter(GsonClassConstants.METHOD_TYPE_FROM_JSON_JSON_READER_TYPE,
new AllAttributeVisitor(
new PeepholeEditor(branchTargetFinder, codeAttributeEditor,
new GsonInstrumentationAdder(programClassPool,
libraryClassPool,
codeAttributeEditor))))))));
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy