proguard.optimize.gson.GsonDeserializationOptimizer 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 proguard.classfile.*;
import proguard.classfile.attribute.visitor.AttributeVisitor;
import proguard.classfile.editor.ClassBuilder;
import proguard.classfile.editor.CompactCodeAttributeComposer;
import proguard.classfile.util.ClassReferenceInitializer;
import proguard.classfile.util.ClassUtil;
import proguard.classfile.util.MethodLinker;
import proguard.classfile.visitor.*;
import proguard.io.ExtraDataEntryNameMap;
import proguard.optimize.info.ProgramMemberOptimizationInfoSetter;
import java.util.*;
import static proguard.classfile.ClassConstants.*;
import static proguard.optimize.gson.OptimizedClassConstants.*;
/**
* This visitor injects a fromJson$xxx() method into the classes that it visits
* that deserializes its fields from Json.
*
* @author Lars Vandenbergh
* @author Rob Coekaerts
*/
public class GsonDeserializationOptimizer
implements ClassVisitor,
MemberVisitor,
AttributeVisitor
{
private static final boolean DEBUG = false;
private static final int IS_NULL_VARIABLE_INDEX = ClassUtil.internalMethodParameterSize(METHOD_TYPE_FROM_JSON_FIELD, false);
private static final Map inlineDeserializers = new HashMap();
private final ClassPool programClassPool;
private final ClassPool libraryClassPool;
private final GsonRuntimeSettings gsonRuntimeSettings;
private final OptimizedJsonInfo deserializationInfo;
private final boolean supportExposeAnnotation;
private final ExtraDataEntryNameMap extraDataEntryNameMap;
private OptimizedJsonInfo.ClassJsonInfo classDeserializationInfo;
private Map javaToJsonFieldNames;
private Map caseLabelByJavaFieldName;
static
{
inlineDeserializers.put(TypeConstants.BYTE + "",
new InlineDeserializers.InlinePrimitiveIntegerDeserializer(byte.class));
inlineDeserializers.put(TypeConstants.SHORT + "",
new InlineDeserializers.InlinePrimitiveIntegerDeserializer(short.class));
inlineDeserializers.put(TypeConstants.INT + "",
new InlineDeserializers.InlinePrimitiveIntegerDeserializer());
inlineDeserializers.put(ClassConstants.TYPE_JAVA_LANG_STRING,
new InlineDeserializers.InlineStringDeserializer());
}
/**
* Creates a new GsonDeserializationOptimizer.
*
* @param programClassPool the program class pool to initialize added
* references.
* @param libraryClassPool the library class pool to initialize added
* references.
* @param gsonRuntimeSettings keeps track of all GsonBuilder invocations.
* @param deserializationInfo contains information on which class and
* fields need to be optimized and how.
* @param extraDataEntryNameMap the map that keeps track of injected
* classes.
*/
public GsonDeserializationOptimizer(ClassPool programClassPool,
ClassPool libraryClassPool,
GsonRuntimeSettings gsonRuntimeSettings,
OptimizedJsonInfo deserializationInfo,
ExtraDataEntryNameMap extraDataEntryNameMap)
{
this.programClassPool = programClassPool;
this.libraryClassPool = libraryClassPool;
this.gsonRuntimeSettings = gsonRuntimeSettings;
this.deserializationInfo = deserializationInfo;
this.supportExposeAnnotation = gsonRuntimeSettings.excludeFieldsWithoutExposeAnnotation;
this.extraDataEntryNameMap = extraDataEntryNameMap;
}
// Implementations for ClassVisitor.
@Override
public void visitAnyClass(Clazz clazz) { }
@Override
public void visitProgramClass(ProgramClass programClass)
{
// Make access public for _OptimizedTypeAdapterFactory and _OptimizedTypeAdapterImpl.
programClass.u2accessFlags &= ~AccessConstants.PRIVATE;
programClass.u2accessFlags |= AccessConstants.PUBLIC;
// Make default constructor public for _OptimizedTypeAdapterImpl.
MemberCounter constructorCounter = new MemberCounter();
programClass.methodsAccept(
new MemberNameFilter(ClassConstants.METHOD_NAME_INIT,
new MemberDescriptorFilter(ClassConstants.METHOD_TYPE_INIT,
new MultiMemberVisitor(
new MemberAccessSetter(AccessConstants.PUBLIC),
constructorCounter))));
// Start adding new deserialization methods.
ClassBuilder classBuilder =
new ClassBuilder(programClass);
if (constructorCounter.getCount() == 0)
{
addDefaultConstructor(programClass,
classBuilder);
}
int classIndex = deserializationInfo.classIndices.get(programClass.getName());
addFromJsonMethod(programClass,
classBuilder,
classIndex);
addFromJsonFieldMethod(programClass,
classBuilder,
classIndex);
// Make sure all references in the class are initialized.
programClass.accept(new ClassReferenceInitializer(programClassPool, libraryClassPool));
// Link all methods with related ones.
programClass.accept(new MethodLinker());
}
private void addDefaultConstructor(ProgramClass programClass,
ClassBuilder classBuilder)
{
if (DEBUG)
{
System.out.println(
"GsonDeserializationOptimizer: adding default constructor to " +
programClass.getName());
}
classBuilder.addMethod(
AccessConstants.PUBLIC |
AccessConstants.SYNTHETIC,
ClassConstants.METHOD_NAME_INIT,
ClassConstants.METHOD_TYPE_INIT,
10,
____ -> ____
.aload_0() // this
.invokespecial(programClass.getSuperName(),
ClassConstants.METHOD_NAME_INIT,
ClassConstants.METHOD_TYPE_INIT)
.return_(),
new ProgramMemberOptimizationInfoSetter());
}
private void addFromJsonMethod(ProgramClass programClass,
ClassBuilder classBuilder,
int classIndex)
{
String methodNameFromJson = METHOD_NAME_FROM_JSON + classIndex;
if (DEBUG)
{
System.out.println(
"GsonDeserializationOptimizer: adding " +
methodNameFromJson +
" method to " + programClass.getName());
}
classBuilder.addMethod(
AccessConstants.PUBLIC | AccessConstants.SYNTHETIC,
methodNameFromJson,
OptimizedClassConstants.METHOD_TYPE_FROM_JSON,
1000,
____ ->
{
// Begin Json object.
____.aload(FromJsonLocals.JSON_READER)
.invokevirtual(GsonClassConstants.NAME_JSON_READER,
GsonClassConstants.METHOD_NAME_READER_BEGIN_OBJECT,
GsonClassConstants.METHOD_TYPE_READER_BEGIN_OBJECT);
// Assign locals for nextFieldIndex.
int nextFieldIndexLocalIndex = 4;
// Start while loop that iterates over Json fields.
CompactCodeAttributeComposer.Label startWhile = ____.createLabel();
CompactCodeAttributeComposer.Label endJsonObject = ____.createLabel();
// Is there a next field? If not, terminate loop and end object.
____.label(startWhile)
.aload(FromJsonLocals.JSON_READER)
.invokevirtual(GsonClassConstants.NAME_JSON_READER,
GsonClassConstants.METHOD_NAME_HAS_NEXT,
GsonClassConstants.METHOD_TYPE_HAS_NEXT)
.ifeq(endJsonObject);
// Get next field index and store it in a local.
____.aload(FromJsonLocals.OPTIMIZED_JSON_READER)
.aload(FromJsonLocals.JSON_READER)
.invokeinterface(OptimizedClassConstants.NAME_OPTIMIZED_JSON_READER,
OptimizedClassConstants.METHOD_NAME_NEXT_FIELD_INDEX,
OptimizedClassConstants.METHOD_TYPE_NEXT_FIELD_INDEX)
.istore(nextFieldIndexLocalIndex);
// Invoke fromJsonField$ with the stored field index.
classDeserializationInfo = deserializationInfo.classJsonInfos.get(programClass.getName());
javaToJsonFieldNames = classDeserializationInfo.javaToJsonFieldNames;
String methodNameFromJsonField = METHOD_NAME_FROM_JSON_FIELD + classIndex;
____.aload(FromJsonLocals.THIS)
.aload(FromJsonLocals.GSON)
.aload(FromJsonLocals.JSON_READER)
.iload(nextFieldIndexLocalIndex)
.invokevirtual(programClass.getName(),
methodNameFromJsonField,
METHOD_TYPE_FROM_JSON_FIELD);
// Jump to start of while loop.
____.goto_(startWhile);
// End Json object.
____.label(endJsonObject)
.aload(FromJsonLocals.JSON_READER)
.invokevirtual(GsonClassConstants.NAME_JSON_READER,
GsonClassConstants.METHOD_NAME_READER_END_OBJECT,
GsonClassConstants.METHOD_TYPE_READER_END_OBJECT)
.return_();
},
new ProgramMemberOptimizationInfoSetter());
}
private void addFromJsonFieldMethod(ProgramClass programClass,
ClassBuilder classBuilder,
int classIndex)
{
String methodNameFromJsonField = METHOD_NAME_FROM_JSON_FIELD + classIndex;
if (DEBUG)
{
System.out.println(
"GsonDeserializationOptimizer: adding " +
methodNameFromJsonField +
" method to " + programClass.getName());
}
classBuilder.addMethod(
AccessConstants.PROTECTED | AccessConstants.SYNTHETIC,
methodNameFromJsonField,
METHOD_TYPE_FROM_JSON_FIELD,
1000,
____ ->
{
CompactCodeAttributeComposer.Label endSwitch = ____.createLabel();
// Are there any fields to be deserialized at the level of this class?
if (javaToJsonFieldNames.size() > 0)
{
// Check for NULL token and store result in boolean local variable.
CompactCodeAttributeComposer.Label tokenNotNull = ____.createLabel();
CompactCodeAttributeComposer.Label assignIsNull = ____.createLabel();
____.aload(FromJsonLocals.JSON_READER)
.invokevirtual(GsonClassConstants.NAME_JSON_READER,
GsonClassConstants.METHOD_NAME_PEEK,
GsonClassConstants.METHOD_TYPE_PEEK)
.getstatic(GsonClassConstants.NAME_JSON_TOKEN,
GsonClassConstants.FIELD_NAME_NULL,
GsonClassConstants.FIELD_TYPE_NULL);
____.ifacmpeq(tokenNotNull)
.iconst_1()
.goto_(assignIsNull)
.label(tokenNotNull)
.iconst_0()
.label(assignIsNull)
.istore(IS_NULL_VARIABLE_INDEX);
generateSwitchTables(____, endSwitch);
}
if (programClass.getSuperClass() != null)
{
// If no known field index was returned for this class and
// field, delegate to super method if it exists or skip the value.
if (!programClass.getSuperClass().getName().equals(ClassConstants.NAME_JAVA_LANG_OBJECT))
{
// Call the superclass fromJsonField$.
Integer superClassIndex =
deserializationInfo.classIndices.get(programClass.getSuperClass().getName());
String superMethodNameFromJsonField =
METHOD_NAME_FROM_JSON_FIELD + superClassIndex;
____.aload(FromJsonFieldLocals.THIS)
.aload(FromJsonFieldLocals.GSON)
.aload(FromJsonFieldLocals.JSON_READER)
.iload(FromJsonFieldLocals.FIELD_INDEX)
.invokevirtual(programClass.getSuperClass().getName(),
superMethodNameFromJsonField,
METHOD_TYPE_FROM_JSON_FIELD);
}
else
{
// Skip field in default case of switch or when no switch is generated.
____.aload(FromJsonLocals.JSON_READER)
.invokevirtual(GsonClassConstants.NAME_JSON_READER,
GsonClassConstants.METHOD_NAME_SKIP_VALUE,
GsonClassConstants.METHOD_TYPE_SKIP_VALUE);
}
}
else
{
throw new RuntimeException ("Cannot find super class of " + programClass.getName() + " for Gson optimization. Please check your configuration includes all the expected library jars.");
}
// End of switch.
____.label(endSwitch)
.return_();
},
new ProgramMemberOptimizationInfoSetter());
}
private void generateSwitchTables(CompactCodeAttributeComposer ____,
CompactCodeAttributeComposer.Label endSwitch)
{
Set exposedJavaFieldNames = classDeserializationInfo.exposedJavaFieldNames;
Set exposedOrAllJavaFieldNames = supportExposeAnnotation ? exposedJavaFieldNames :
javaToJsonFieldNames.keySet();
generateSwitchTable(____,
endSwitch,
javaToJsonFieldNames,
exposedOrAllJavaFieldNames);
if (supportExposeAnnotation)
{
// Runtime check whether excludeFieldsWithoutExposeAnnotation is enabled.
// If so, skip this switch statement.
CompactCodeAttributeComposer.Label nonExposedCasesEnd = ____.createLabel();
____.aload(ToJsonLocals.GSON)
.getfield(GsonClassConstants.NAME_GSON, FIELD_NAME_EXCLUDER, FIELD_TYPE_EXCLUDER)
.getfield(GsonClassConstants.NAME_EXCLUDER, FIELD_NAME_REQUIRE_EXPOSE, FIELD_TYPE_REQUIRE_EXPOSE)
.ifne(nonExposedCasesEnd);
Set nonExposedJavaFieldNames = new HashSet();
for (String javaFieldName: javaToJsonFieldNames.keySet())
{
if (!exposedJavaFieldNames.contains(javaFieldName))
{
nonExposedJavaFieldNames.add((javaFieldName));
}
}
generateSwitchTable(____,
endSwitch,
javaToJsonFieldNames,
nonExposedJavaFieldNames);
____.label(nonExposedCasesEnd);
}
}
private void generateSwitchTable(CompactCodeAttributeComposer ____,
CompactCodeAttributeComposer.Label endSwitch,
Map javaToJsonFieldNames,
Set javaFieldNamesToProcess)
{
ArrayList fromJsonFieldCases = new ArrayList();
for (Map.Entry javaToJsonFieldNameEntry : javaToJsonFieldNames.entrySet())
{
if (javaFieldNamesToProcess.contains(javaToJsonFieldNameEntry.getKey()))
{
// Add cases for the alternative Json names with the same label.
String[] jsonFieldNames = javaToJsonFieldNameEntry.getValue();
CompactCodeAttributeComposer.Label caseLabel = ____.createLabel();
for (String jsonFieldName : jsonFieldNames)
{
fromJsonFieldCases.add(new FromJsonFieldCase(javaToJsonFieldNameEntry.getKey(),
caseLabel,
deserializationInfo.jsonFieldIndices.get(jsonFieldName)));
}
}
}
Collections.sort(fromJsonFieldCases);
// Don't add switch cases for fields for which we won't create deserialization code later,
// otherwise we'll end up with dangling labels.
fromJsonFieldCases.removeIf(fromJsonFieldCase -> {
Field field = ____.getTargetClass().findField(fromJsonFieldCase.javaFieldName, null);
return field == null ||
(field.getAccessFlags() & (AccessConstants.STATIC | AccessConstants.SYNTHETIC)) != 0;
});
int[] cases = new int[fromJsonFieldCases.size()];
CompactCodeAttributeComposer.Label[] jumpOffsets = new CompactCodeAttributeComposer.Label[fromJsonFieldCases.size()];
caseLabelByJavaFieldName = new HashMap();
for (int caseIndex = 0; caseIndex < fromJsonFieldCases.size(); caseIndex++)
{
FromJsonFieldCase fromJsonFieldCase = fromJsonFieldCases.get(caseIndex);
cases[caseIndex] = fromJsonFieldCase.fieldIndex;
jumpOffsets[caseIndex] = fromJsonFieldCase.label;
caseLabelByJavaFieldName.put(fromJsonFieldCase.javaFieldName, fromJsonFieldCase.label);
}
CompactCodeAttributeComposer.Label defaultCase = ____.createLabel();
____.iload(FromJsonFieldLocals.FIELD_INDEX)
.lookupswitch(defaultCase,
cases,
jumpOffsets);
// Apply non static member visitor to all fields to visit.
____.getTargetClass().fieldsAccept(
new MemberAccessFilter(0,
AccessConstants.SYNTHETIC |
AccessConstants.STATIC,
new FromJsonFieldDeserializationCodeAdder(____, endSwitch)));
____.label(defaultCase);
}
private class FromJsonFieldDeserializationCodeAdder
implements MemberVisitor
{
private final CompactCodeAttributeComposer ____;
private final CompactCodeAttributeComposer.Label endSwitch;
public FromJsonFieldDeserializationCodeAdder(CompactCodeAttributeComposer ____,
CompactCodeAttributeComposer.Label endSwitch)
{
this.____ = ____;
this.endSwitch = endSwitch;
}
// Implementations for MemberVisitor.
@Override
public void visitProgramField(ProgramClass programClass,
ProgramField programField)
{
CompactCodeAttributeComposer.Label fromJsonFieldCaseLabel =
caseLabelByJavaFieldName.get(programField.getName(programClass));
if (fromJsonFieldCaseLabel != null)
{
// Make sure the field is not final anymore so we can safely write it from the injected method.
programField.accept(programClass, new MemberAccessFlagCleaner(AccessConstants.FINAL));
// Check if value is null
CompactCodeAttributeComposer.Label isNull = ____.createLabel();
____.label(fromJsonFieldCaseLabel)
.iload(IS_NULL_VARIABLE_INDEX)
.ifeq(isNull);
String fieldDescriptor = programField.getDescriptor(programClass);
FieldSignatureCollector signatureAttributeCollector = new FieldSignatureCollector();
programField.attributesAccept(programClass, signatureAttributeCollector);
InlineDeserializer inlineDeserializer = inlineDeserializers.get(fieldDescriptor);
if (!gsonRuntimeSettings.registerTypeAdapterFactory &&
inlineDeserializer != null &&
inlineDeserializer.canDeserialize(gsonRuntimeSettings))
{
inlineDeserializer.deserialize(programClass,
programField,
____,
gsonRuntimeSettings);
}
else
{
// Derive the field class and type name for which we want to retrieve the type adapter from Gson.
String fieldTypeName;
String fieldClassName;
if (ClassUtil.isInternalPrimitiveType(fieldDescriptor))
{
fieldClassName = ClassUtil.internalNumericClassNameFromPrimitiveType(fieldDescriptor.charAt(0));
fieldTypeName = fieldClassName;
}
else
{
fieldClassName = ClassUtil.internalClassNameFromClassType(fieldDescriptor);
fieldTypeName = ClassUtil.internalClassTypeFromType(fieldDescriptor);
}
// Derive type token class name if there is a field signature.
String typeTokenClassName = null;
if (signatureAttributeCollector.getFieldSignature() != null)
{
// Add type token sub-class that has the appropriate type parameter.
ProgramClass typeTokenClass =
new TypeTokenClassBuilder(programClass,
programField,
signatureAttributeCollector.getFieldSignature())
.build(programClassPool);
programClassPool.addClass(typeTokenClass);
typeTokenClass.accept(new ClassReferenceInitializer(programClassPool,
libraryClassPool));
typeTokenClassName = typeTokenClass.getName();
extraDataEntryNameMap.addExtraClassToClass(programClass.getName(),
typeTokenClassName);
}
// Retrieve type adapter and deserialize value from Json.
if (typeTokenClassName == null)
{
____.aload(FromJsonLocals.THIS)
.aload(FromJsonLocals.GSON)
.ldc(fieldTypeName, programClassPool.getClass(fieldClassName))
.invokevirtual(GsonClassConstants.NAME_GSON,
GsonClassConstants.METHOD_NAME_GET_ADAPTER_CLASS,
GsonClassConstants.METHOD_TYPE_GET_ADAPTER_CLASS);
}
else
{
____.aload(FromJsonLocals.THIS)
.aload(FromJsonLocals.GSON)
.new_(typeTokenClassName)
.dup()
.invokespecial(typeTokenClassName,
ClassConstants.METHOD_NAME_INIT,
ClassConstants.METHOD_TYPE_INIT)
.invokevirtual(GsonClassConstants.NAME_GSON,
GsonClassConstants.METHOD_NAME_GET_ADAPTER_TYPE_TOKEN,
GsonClassConstants.METHOD_TYPE_GET_ADAPTER_TYPE_TOKEN);
}
____.aload(FromJsonLocals.JSON_READER)
.invokevirtual(GsonClassConstants.NAME_TYPE_ADAPTER,
GsonClassConstants.METHOD_NAME_READ,
GsonClassConstants.METHOD_TYPE_READ)
.checkcast(fieldTypeName, programClassPool.getClass(fieldClassName));
// If the field is primitive, unbox the value before assigning it.
switch (fieldDescriptor.charAt(0))
{
case TypeConstants.BOOLEAN:
____.invokevirtual(NAME_JAVA_LANG_BOOLEAN,
METHOD_NAME_BOOLEAN_VALUE,
METHOD_TYPE_BOOLEAN_VALUE);
break;
case TypeConstants.BYTE:
____.invokevirtual(NAME_JAVA_LANG_BYTE,
METHOD_NAME_BYTE_VALUE,
METHOD_TYPE_BYTE_VALUE);
break;
case TypeConstants.CHAR:
____.invokevirtual(NAME_JAVA_LANG_CHARACTER,
METHOD_NAME_CHAR_VALUE,
METHOD_TYPE_CHAR_VALUE);
break;
case TypeConstants.SHORT:
____.invokevirtual(NAME_JAVA_LANG_SHORT,
METHOD_NAME_SHORT_VALUE,
METHOD_TYPE_SHORT_VALUE);
break;
case TypeConstants.INT:
____.invokevirtual(NAME_JAVA_LANG_INTEGER,
METHOD_NAME_INT_VALUE,
METHOD_TYPE_INT_VALUE);
break;
case TypeConstants.LONG:
____.invokevirtual(NAME_JAVA_LANG_LONG,
METHOD_NAME_LONG_VALUE,
METHOD_TYPE_LONG_VALUE);
break;
case TypeConstants.FLOAT:
____.invokevirtual(NAME_JAVA_LANG_FLOAT,
METHOD_NAME_FLOAT_VALUE,
METHOD_TYPE_FLOAT_VALUE);
break;
case TypeConstants.DOUBLE:
____.invokevirtual(NAME_JAVA_LANG_DOUBLE,
METHOD_NAME_DOUBLE_VALUE,
METHOD_TYPE_DOUBLE_VALUE);
break;
}
// Assign deserialized value to field.
____.putfield(programClass, programField);
}
// Jump to the end of the switch.
____.goto_(endSwitch);
// Either skip the null (in case of a primitive) or assign the null
// (in case of an object) and jump to the end of the switch.
____.label(isNull);
// Why is it necessary to specifically assign a null value?
if (!ClassUtil.isInternalPrimitiveType(fieldDescriptor))
{
____.aload(FromJsonLocals.THIS)
.aconst_null()
.putfield(programClass, programField);
}
____.aload(FromJsonLocals.JSON_READER)
.invokevirtual(GsonClassConstants.NAME_JSON_READER,
GsonClassConstants.METHOD_NAME_NEXT_NULL,
GsonClassConstants.METHOD_TYPE_NEXT_NULL)
.goto_(endSwitch);
}
}
}
private static class FromJsonFieldCase implements Comparable
{
private String javaFieldName;
private CompactCodeAttributeComposer.Label label;
private int fieldIndex;
public FromJsonFieldCase(String javaFieldName,
CompactCodeAttributeComposer.Label label,
int fieldIndex)
{
this.javaFieldName = javaFieldName;
this.label = label;
this.fieldIndex = fieldIndex;
}
@Override
public int compareTo(FromJsonFieldCase fromJsonFieldCase)
{
return this.fieldIndex - fromJsonFieldCase.fieldIndex;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy